Source browser
tests/test_shader_compiler.py
Read the complete file below or download its original bytes. Line numbers provide stable links to a declaration or processing step.
File fingerprint
1#!/usr/bin/env python3
2"""Exercise native GLSL frontend semantics, diagnostics and C++11 adapter execution."""
3
4import argparse # Read the build or test command options.
5import importlib.util # Load the local frontend directly for these regression checks.
6from pathlib import Path # Read shader inputs and write generated adapter paths.
7import subprocess # Compile and execute generated adapters or validate GLSL text.
8import tempfile # Keep temporary generated files outside the project source tree.
9import unittest # Describe compiler behavior as executable regression checks.
10
11ROOT = Path(__file__).resolve().parents[1] # Compute ROOT for the following operation.
12SPEC = importlib.util.spec_from_file_location( # Compute SPEC for the following operation.
13 "shdr_compile_shader", ROOT / "scripts" / "compile_shader.py" # Supply shdr compile shader for the continued expression.
14) # Finish this expression or collection.
15COMPILER = importlib.util.module_from_spec(SPEC) # Compute COMPILER for the following operation.
16SPEC.loader.exec_module(COMPILER) # Apply exec module to these arguments.
17CPP_COMPILER = None # Compute CPP COMPILER for the following operation.
18GLSLANG_VALIDATOR = None # Compute GLSLANG VALIDATOR for the following operation.
19EGL_FLAGS = [] # Compute EGL FLAGS for the following operation.
20
21
22class FrontendTests(unittest.TestCase): # Group these regression checks.
23 """Check interface-validation diagnostics during adapter generation."""
24
25 def test_comments_and_literals_do_not_declare_interfaces(self): # Verify interface parsing preserves comments and literals.
26 source = '''
27// layout(local_size_x=0) in; shared atomicAdd barrier
28/* uniform sampler2D fake; layout(std140,binding=0) buffer Broken {}; */
29layout(local_size_x=2, local_size_y=3) in;
30const char* diagnostic = "uniform sampler2D fake; atomicAdd barrier .rgba";
31void main() {}
32'''
33 cpu, gpu, local = COMPILER.transform(source, "lexical_kernel") # Apply transform to these arguments.
34 self.assertEqual(local, [2, 3, 1]) # Check the expected frontend result or execution status.
35 self.assertIn('"uniform sampler2D fake; atomicAdd barrier .rgba"', cpu) # Check the expected frontend result or execution status.
36 self.assertEqual( # Check the expected frontend result or execution status.
37 gpu, # Supply gpu for the continued expression.
38 source.replace( # Apply replace to these arguments.
39 "layout(local_size_x=2, local_size_y=3) in;",
40 "layout(local_size_x=2,local_size_y=3,local_size_z=1) in;",
41 ), # Finish this expression or collection.
42 ) # Finish this expression or collection.
43 masked = COMPILER.mask_noncode(source) # Compute masked for the following operation.
44 self.assertEqual(len(masked), len(source)) # Check the expected frontend result or execution status.
45 self.assertEqual( # Check the expected frontend result or execution status.
46 [i for i, c in enumerate(masked) if c == "\n"], # Apply enumerate to these arguments.
47 [i for i, c in enumerate(source) if c == "\n"], # Apply enumerate to these arguments.
48 ) # Finish this expression or collection.
49 for annotation in ('// Include CPU types.', '/* Include CPU types. */'): # Process each annotation in this sequence.
50 annotated = ( # Compute annotated for the following operation.
51 '#ifdef __cplusplus\n#include "shdr.h" '
52 + annotation # Supply annotation for the continued expression.
53 + '\n#endif\nvoid main(){}'
54 ) # Finish this expression or collection.
55 _, gpu, _ = COMPILER.transform(annotated, "annotated_include_kernel") # Apply transform to these arguments.
56 self.assertNotIn('#include', gpu) # Check the expected frontend result or execution status.
57
58 def test_rejects_unsupported_declarations(self): # Verify declaration validation diagnostics.
59 base = "layout(local_size_x=1) in;\n{}\nvoid main() {{}}\n" # Compute base for the following operation.
60 cases = [ # Compute cases for the following operation.
61 ("layout(std140,binding=0) buffer B {float values[];};", "std430"), # Apply layout to these arguments.
62 ("layout(std430,binding=0) buffer B {vec3 values[];};", "scalar/vec2/vec4"), # Apply layout to these arguments.
63 ( # Finish this expression or collection.
64 "layout(std430,binding=0) buffer B {float a; float values[];};",
65 "one runtime-sized",
66 ), # Finish this expression or collection.
67 ( # Finish this expression or collection.
68 "layout(std430,binding=0) readonly writeonly buffer B {float values[];};",
69 "both readonly",
70 ), # Finish this expression or collection.
71 ( # Finish this expression or collection.
72 "layout(std430,binding=0) buffer A {float a[];};\n"
73 "layout(std430,binding=0) buffer B {float b[];};",
74 "duplicate buffer binding",
75 ), # Finish this expression or collection.
76 ("uniform float value; uniform uint value;", "duplicate resource"), # Supply uniform for the continued expression.
77 ("uniform sampler2D source;", "unsupported uniform type"), # Supply uniform for the continued expression.
78 ("layout(r32f,binding=0) uniform iimage2D source;", "type and format"), # Apply layout to these arguments.
79 ("layout(rgba32f,binding=0) uniform image2D source;", "RGBA images"), # Apply layout to these arguments.
80 ( # Finish this expression or collection.
81 "layout(rgba8,binding=0) readonly uniform image2D source;",
82 "supported r32/rgba32",
83 ), # Finish this expression or collection.
84 ( # Finish this expression or collection.
85 "layout(r32f,binding=0) readonly uniform image2D a;\n"
86 "layout(r32f,binding=0) writeonly uniform image2D b;",
87 "duplicate image binding",
88 ), # Finish this expression or collection.
89 ("shared float values[16];", "outside the CPU portable subset"), # Supply shared for the continued expression.
90 ("void wait_for_group(){barrier();}", "outside the CPU portable subset"), # Apply wait for group to these arguments.
91 ( # Finish this expression or collection.
92 "void atomic_update(){atomicAdd(counter,1u);}",
93 "outside the CPU portable subset",
94 ), # Finish this expression or collection.
95 ] # Finish this expression or collection.
96 for declaration, message in cases: # Process each declaration, message in this sequence.
97 with self.subTest(declaration=declaration): # Keep this temporary resource alive throughout the following block.
98 with self.assertRaisesRegex(COMPILER.ShaderError, message): # Keep this temporary resource alive throughout the following block.
99 COMPILER.generate(base.format(declaration), "invalid_kernel") # Apply generate to these arguments.
100
101 for source, message in [ # Process each source, message in this sequence.
102 ("layout(local_size_x=0) in; void main(){}", "positive"), # Apply layout to these arguments.
103 ( # Finish this expression or collection.
104 "layout(local_size_x=1,local_size_x=2) in; void main(){}",
105 "duplicate layout",
106 ), # Finish this expression or collection.
107 ( # Finish this expression or collection.
108 "layout(local_size_x=1, local_size_x = 2) in; void main(){}",
109 "duplicate layout",
110 ), # Finish this expression or collection.
111 ("layout(local_size_x=-1) in; void main(){}", "nonnegative integer"), # Apply layout to these arguments.
112 ("layout(local_size_x=1) in; void helper(){}", "exactly one void main"), # Apply layout to these arguments.
113 ( # Finish this expression or collection.
114 "layout(local_size_x=1) in; void main(){} void main(){}",
115 "exactly one void main",
116 ), # Finish this expression or collection.
117 ( # Finish this expression or collection.
118 '#include "other.h"\nlayout(local_size_x=1) in; void main(){}',
119 "includes are unsupported",
120 ), # Finish this expression or collection.
121 ]: # Finish this expression or collection.
122 with self.subTest(source=source): # Keep this temporary resource alive throughout the following block.
123 with self.assertRaisesRegex(COMPILER.ShaderError, message): # Keep this temporary resource alive throughout the following block.
124 COMPILER.generate(source, "invalid_kernel") # Apply generate to these arguments.
125
126 def test_generated_cpp_preserves_native_glsl_semantics(self): # Verify generated cpp preserves native glsl semantics behavior.
127 # in parameters may be mutated in GLSL, but the caller's original is unchanged.
128 # The block instance and all three native swizzle naming families are exercised.
129 source = '''
130#ifdef __cplusplus
131#include "shdr.h"
132#endif
133layout(local_size_x=1) in;
134layout(std430,binding=0) writeonly buffer Values {float r[];} state;
135layout(std430,binding=1) readonly buffer Vectors {vec2 xy[];} vectors;
136void adjust(in vec4 value, out vec2 selected, inout uint steps) {
137 value.r += 1.0;
138 value.yx = value.xy;
139 selected = value.st;
140 steps += 1u;
141}
142void main() {
143 vec4 original = vec4(1.0,7.0,3.0,4.0);
144 vec2 selected = vec2(0.0);
145 uint steps = 3u;
146 adjust(original,selected,steps);
147 state.r[0] = original.r;
148 state.r[1] = selected.s;
149 state.r[2] = selected.t;
150 state.r[3] = float(steps);
151 state.r[4] = float(state.r.length());
152 float fraction = 0.5;
153 fraction = clamp(fraction,0.0,1.0);
154 state.r[5] = pow(fraction,2.0) + min(fraction,0.25) + max(fraction,0.5)
155 + mix(0.0,1.0,fraction);
156 state.r[6] = 1e-3;
157 vec2 pair = vectors.xy[0].xy;
158 pair.yx = pair.xy;
159 ((state)).r[7] = pair.x;
160 ((state)).r[8] = ((vectors)).xy[0].xy.y;
161}
162'''
163 application = '''
164#include "shdr.h"
165int main() {
166 shdr::buffer<float> result(9);
167 shdr::buffer<shdr::glsl::vec2> input{shdr::glsl::vec2(2.0f,3.0f)};
168 auto shader = shdr::make_kernel("semantics_kernel");
169 shader->bind("state.r",result).bind("vectors.xy",input);
170 shdr::pipeline pipeline;
171 pipeline += shdr::dispatch(*shader,shdr::extent(1));
172 pipeline.run();
173 const shdr::buffer<float>& read = result;
174 const float expected[] = {1.0f,7.0f,2.0f,4.0f,9.0f,1.5f,1e-3f,3.0f,3.0f};
175 for(unsigned i=0;i<9;++i) if(read.data()[i]!=expected[i]) return 1;
176}
177'''
178 with tempfile.TemporaryDirectory(prefix="shdr-front-end-") as directory: # Keep this temporary resource alive throughout the following block.
179 path = Path(directory) # Compute path for the following operation.
180 adapter = path / "adapter.cpp" # Compute adapter for the following operation.
181 adapter.write_text(COMPILER.generate(source, "semantics_kernel")) # Apply write text to these arguments.
182 main = path / "main.cpp" # Compute main for the following operation.
183 main.write_text(application) # Apply write text to these arguments.
184 executable = path / "semantics" # Compute executable for the following operation.
185 subprocess.run( # Run the compiler, validator or generated executable.
186 [ # Finish this expression or collection.
187 CPP_COMPILER, # Supply CPP COMPILER for the continued expression.
188 "-std=c++11",
189 "-pedantic-errors",
190 "-I",
191 str(ROOT), # Apply str to these arguments.
192 str(adapter), # Apply str to these arguments.
193 str(main), # Apply str to these arguments.
194 "-o",
195 str(executable), # Apply str to these arguments.
196 ], # Finish this expression or collection.
197 check=True, # Compute check for the following operation.
198 ) # Finish this expression or collection.
199 subprocess.run([str(executable)], check=True, cwd=directory) # Run the compiler, validator or generated executable.
200
201 def test_desktop_numeric_promotions_on_all_profiles(self): # Verify desktop numeric promotions on all profiles behavior.
202 source = '''
203layout(local_size_x=1) in;
204layout(std430,binding=0) buffer Values {float result[];};
205layout(std430,binding=1) readonly buffer InputValues {float items[];} input_data;
206uniform float runtime_value;
207uniform uint unsigned_value;
208uniform int signed_value;
209const int GLOBAL_COUNT=4;
210uint fibonacci(uint n) { if(n<=1) return n; return 2u; }
211float scalar_argument(const float input_value) { return input_value*0.5; }
212void main() {
213 const int LOCAL_COUNT=GLOBAL_COUNT/2;
214 float coefficients[LOCAL_COUNT];
215 coefficients[0]=1.0;
216 coefficients[1]=2.0;
217 const float runtime_constant=runtime_value+1.0;
218 const uint runtime_count=unsigned_value;
219 result[0]=unsigned_value;
220 result[1]=runtime_value/signed_value;
221 result[2]=1.5/2;
222 result[3]=float(fibonacci(unsigned_value));
223 result[4]=scalar_argument(4);
224 ivec2 integers=mix(ivec2(1,2),ivec2(3,4),bvec2(true,false));
225 uvec2 unsigned_integers=mix(uvec2(5,6),uvec2(7,8),bvec2(false,true));
226 result[5]=float(integers.x+integers.y);
227 result[6]=float(unsigned_integers.x+unsigned_integers.y);
228 result[7]=unsigned_value>0 ? runtime_value : 3;
229 result[8]=float(unsigned_value==-1);
230 result[9]=float(-1<unsigned_value);
231 vec2 pair=vec2(runtime_value)/signed_value;
232 result[10]=pair.x+runtime_constant+float(runtime_count)+coefficients[1];
233 result[11]=mix(0.0,2.0,0.5);
234 result[12]=float(unsigned_value<result.length());
235 result[13]=float(17u>=result.length());
236 result[14]=float(-1<result.length());
237 result[15]=float(unsigned_value<input_data.items.length());
238 result[16]=float(-1<((input_data)).items.length())+float(coefficients.length());
239}
240'''
241 application = '''
242#include "shdr.h"
243#include <cmath>
244#include <iostream>
245int main(int argc,char** argv) {
246 auto shader=shdr::make_kernel("promotion_kernel");
247 if(argc==2) {
248 const std::string name(argv[1]);
249 const shdr::profile profile=name=="es31"?shdr::profile::es31:
250 name=="es32"?shdr::profile::es32:name=="gl43"?shdr::profile::gl43:
251 name=="gl45"?shdr::profile::gl45:shdr::profile::gl46;
252 std::cout<<shader->glsl(profile);
253 return 0;
254 }
255 shdr::buffer<float> result(17), input{2.0f,4.0f};
256 shader->bind("result",result).bind("input_data.items",input).set<float>("runtime_value",6.0f)
257 .set<unsigned>("unsigned_value",3u).set<int>("signed_value",2);
258 shdr::pipeline pipeline=shdr::dispatch(*shader,shdr::extent(1));
259 pipeline.run();
260 const shdr::buffer<float>& read=result;
261 const float expected[]={3.0f,3.0f,0.75f,2.0f,2.0f,5.0f,13.0f,6.0f,0.0f,0.0f,15.0f,1.0f,1.0f,1.0f,1.0f,0.0f,3.0f};
262 for(unsigned i=0;i<17;++i) if(std::abs(read.data()[i]-expected[i])>1e-6f) return 1;
263#ifdef SHDR_ENABLE_EGL
264 try {
265 shdr::headless_device device;
266 pipeline.run(device);
267 for(unsigned i=0;i<17;++i) if(std::abs(read.data()[i]-expected[i])>1e-6f) return 2;
268 } catch(const shdr::backend_unavailable&) { return 77; }
269#endif
270 return 0;
271}
272'''
273 _, gpu, _ = COMPILER.transform(source, "promotion_kernel") # Apply transform to these arguments.
274 self.assertIn('const int LOCAL_COUNT', gpu) # Check the expected frontend result or execution status.
275 self.assertNotIn('const float runtime_constant', gpu) # Check the expected frontend result or execution status.
276 self.assertNotIn('const uint runtime_count', gpu) # Check the expected frontend result or execution status.
277 with tempfile.TemporaryDirectory(prefix="shdr-promotion-test-") as directory: # Keep this temporary resource alive throughout the following block.
278 path = Path(directory) # Compute path for the following operation.
279 adapter, main, executable = ( # Supply adapter for the continued expression.
280 path / 'adapter.cpp', # Supply path for the continued expression.
281 path / 'main.cpp', # Supply path for the continued expression.
282 path / 'promotions', # Supply path for the continued expression.
283 ) # Finish this expression or collection.
284 adapter.write_text(COMPILER.generate(source, "promotion_kernel")) # Apply write text to these arguments.
285 main.write_text(application) # Apply write text to these arguments.
286 command = [ # Compute command for the following operation.
287 CPP_COMPILER, # Supply CPP COMPILER for the continued expression.
288 '-std=c++11',
289 '-pedantic-errors',
290 '-I',
291 str(ROOT), # Apply str to these arguments.
292 str(adapter), # Apply str to these arguments.
293 str(main), # Apply str to these arguments.
294 '-o',
295 str(executable), # Apply str to these arguments.
296 ] # Finish this expression or collection.
297 subprocess.run(command, check=True) # Run the compiler, validator or generated executable.
298 subprocess.run([str(executable)], check=True) # Run the compiler, validator or generated executable.
299 if GLSLANG_VALIDATOR: # Select this branch when its condition holds.
300 for profile in ('es31', 'es32', 'gl43', 'gl45', 'gl46'): # Process each profile in this sequence.
301 exported = subprocess.run( # Compute exported for the following operation.
302 [str(executable), profile], # Apply str to these arguments.
303 check=True, # Compute check for the following operation.
304 stdout=subprocess.PIPE, # Compute stdout for the following operation.
305 text=True, # Compute text for the following operation.
306 ).stdout # Supply stdout for the continued expression.
307 shader = path / (profile + '.comp') # Compute shader for the following operation.
308 shader.write_text(exported) # Apply write text to these arguments.
309 subprocess.run( # Run the compiler, validator or generated executable.
310 [GLSLANG_VALIDATOR, '-S', 'comp', str(shader)], check=True # Apply str to these arguments.
311 ) # Finish this expression or collection.
312 # A development machine with EGL also checks actual GLES values after translation.
313 if EGL_FLAGS: # Select this branch when its condition holds.
314 subprocess.run( # Run the compiler, validator or generated executable.
315 command + ['-DSHDR_ENABLE_EGL=1'] + EGL_FLAGS, check=True # Supply command for the continued expression.
316 ) # Finish this expression or collection.
317 completed = subprocess.run([str(executable)]) # Compute completed for the following operation.
318 self.assertIn(completed.returncode, (0, 77)) # Check the expected frontend result or execution status.
319
320 def test_private_global_initializers_are_per_invocation(self): # Recreate private globals while retaining linked resources and snapshotted uniforms.
321 source = '''
322layout(local_size_x=2) in;
323layout(std430,binding=0) writeonly buffer Results {float values[];} state;
324layout(r32f,binding=0) readonly uniform highp image2D image_values;
325layout(push_constant) uniform Params {float boost;} p;
326uniform float gain;
327uniform uint count;
328#define DECLARE_PRIVATE(name) float name=0.0; float scratch[2]=float[2](2.0,4.0);
329DECLARE_PRIVATE(counter)
330void update_private() {
331 counter+=1.0+p.boost;
332 scratch[0]+=gain+imageLoad(image_values,ivec2(0)).x;
333 scratch[1]-=2.0;
334}
335void main() {
336 uint index=gl_GlobalInvocationID.x;
337 if(index>=count) return;
338 update_private();
339 state.values[3u*index]=counter;
340 state.values[3u*index+1u]=scratch[0]-4.0;
341 state.values[3u*index+2u]=scratch[1]-1.0;
342}
343'''
344 uniform_initializer = '''
345layout(local_size_x=1) in;
346layout(std430,binding=0) writeonly buffer Results {float values[];};
347uniform float seed;
348float counter=seed;
349float scratch[2]=float[2](seed,seed+1.0);
350void main() {
351 uint index=gl_GlobalInvocationID.x;
352 counter+=scratch[0]+scratch[1];
353 values[index]=counter;
354}
355'''
356 application = '''
357#include "shdr.h"
358#include <iostream>
359void verify(const shdr::buffer<float>& result,float first,float second,float third) {
360 for(unsigned i=0;i<result.size();i+=3u)
361 if(result.data()[i]!=first || result.data()[i+1u]!=second || result.data()[i+2u]!=third)
362 throw shdr::error("private globals were shared across invocations or dispatches");
363}
364int main(int argc,char** argv) {
365 auto shader=shdr::make_kernel("private_global_kernel");
366 if(argc==2) {
367 const std::string name(argv[1]);
368 const shdr::profile profile=name=="es31"?shdr::profile::es31:name=="es32"?shdr::profile::es32:
369 name=="gl43"?shdr::profile::gl43:name=="gl45"?shdr::profile::gl45:shdr::profile::gl46;
370 std::cout<<shader->glsl(profile);
371 return 0;
372 }
373 shdr::buffer<float> result(18), other(18);
374 shdr::image2d<float,1> image(1,1);
375 shader->bind("state.values",result).bind("image_values",image)
376 .set<float>("gain",3.0f).set<float>("p.boost",0.0f).set<unsigned>("count",6u);
377 shdr::pipeline pipeline=shdr::dispatch(*shader,shdr::extent(3));
378 pipeline.run();
379 verify(result,1.0f,1.0f,1.0f);
380 pipeline.run();
381 verify(result,1.0f,1.0f,1.0f);
382 shdr::executor worker(1,1);
383 auto done=worker.submit(pipeline);
384 shader->bind("state.values",other).set<float>("gain",7.0f);
385 done.get();
386 verify(result,1.0f,1.0f,1.0f);
387 verify(other,0.0f,0.0f,0.0f);
388 pipeline.run();
389 verify(other,1.0f,5.0f,1.0f);
390 shader->bind("state.values",result).set<float>("gain",3.0f);
391 auto initialized=shdr::make_kernel("bound_initializer_kernel");
392 shdr::buffer<float> seeded(4);
393 initialized->bind("values",seeded).set<float>("seed",2.0f);
394 shdr::pipeline initialized_pipeline=shdr::dispatch(*initialized,shdr::extent(4));
395 initialized_pipeline.run();
396 const shdr::buffer<float>& read_seeded=seeded;
397 for(unsigned i=0;i<4u;++i) if(read_seeded.data()[i]!=7.0f) return 1;
398 initialized->set<float>("seed",4.0f);
399 initialized_pipeline.run();
400 for(unsigned i=0;i<4u;++i) if(read_seeded.data()[i]!=13.0f) return 2;
401#ifdef SHDR_ENABLE_EGL
402 try {
403 shdr::headless_device device;
404 pipeline.run(device);
405 verify(result,1.0f,1.0f,1.0f);
406 pipeline.run(device);
407 verify(result,1.0f,1.0f,1.0f);
408 } catch(const shdr::backend_unavailable&) { return 77; }
409#endif
410 return 0;
411}
412'''
413 with tempfile.TemporaryDirectory(prefix="shdr-private-global-test-") as directory: # Keep generated adapters and executions isolated from project files.
414 path = Path(directory) # Resolve all regression artifacts within the temporary directory.
415 adapter, seeded_adapter, main, executable = path/'adapter.cpp', path/'seeded.cpp', path/'main.cpp', path/'private-globals' # Give each factory its own standard C++ translation unit.
416 adapter.write_text(COMPILER.generate(source,"private_global_kernel")) # Compile the same private globals, helper, buffers, image and uniforms used by GPU.
417 seeded_adapter.write_text(COMPILER.generate(uniform_initializer,"bound_initializer_kernel")) # Check that global initializer values are read after resource binding.
418 main.write_text(application) # Exercise repeated calls and an owned asynchronous kernel snapshot.
419 command=[CPP_COMPILER,'-std=c++11','-pedantic-errors','-pthread','-I',str(ROOT),str(adapter),str(seeded_adapter),str(main),'-o',str(executable)] # Compile both adapters with the required baseline language and executor support.
420 subprocess.run(command,check=True) # Reject any C++11 portability or frame-reference errors.
421 subprocess.run([str(executable)],check=True) # Check every scalar result across invocations, repeat dispatches and a snapshot.
422 if GLSLANG_VALIDATOR: # Validate the unchanged GPU variant in every supported language profile.
423 for profile in ('es31','es32','gl43','gl45','gl46'): # Cover both GLES versions and every desktop target.
424 exported=subprocess.run([str(executable),profile],check=True,stdout=subprocess.PIPE,text=True).stdout # Export the same source embedded with the registered CPU kernel.
425 shader=path/(profile+'.comp') # Keep each profile's source separate for diagnostics.
426 shader.write_text(exported) # Validate the actual embedded portable shader text.
427 subprocess.run([GLSLANG_VALIDATOR,'-S','comp',str(shader)],check=True) # Require every profile to accept the private-global source.
428 if EGL_FLAGS: # A development machine with EGL also executes the actual GLES private-state behavior.
429 subprocess.run(command+['-DSHDR_ENABLE_EGL=1']+EGL_FLAGS,check=True) # Enable the optional library backend for the same shader sources.
430 completed=subprocess.run([str(executable)]) # Compare the CPU results with two actual GLES dispatches.
431 self.assertIn(completed.returncode,(0,77)) # Skip only an unavailable optional context and reject computation failures.
432 self.assertTrue(COMPILER.needs_invocation_frame('float first,second=0.0;void main(){}')) # Detect an initialized later declarator in the same global statement.
433 self.assertFalse(COMPILER.needs_invocation_frame('const float first=0.0;void main(){}')) # Keep immutable global constants on the byte-identical fast path.
434 self.assertTrue(COMPILER.needs_invocation_frame('#define PRIVATE_STATE float counter=0.0;\nPRIVATE_STATE\nvoid main(){}')) # Detect an object macro hiding an initialized global declaration.
435 self.assertTrue(COMPILER.needs_invocation_frame('#define PRIVATE_STATE \\\nfloat counter=0.0;\nPRIVATE_STATE\nvoid main(){}')) # Retain assignment detection across a continued macro definition.
436
437
438 def test_imported_layouts_fixed_arrays_and_helpers(self): # Verify imported layouts fixed arrays and helpers behavior.
439 source = '''
440#version 430 core
441#define BASE (1u << 0u)
442#define COUNT 4
443const uint INPUT_BINDING = BASE | 0u;
444layout(std430) buffer;
445layout(local_size_x=BASE) in;
446layout(binding=INPUT_BINDING) readonly buffer Inputs {float items[COUNT];} inputs;
447layout(binding=BASE+1) writeonly buffer Outputs {float results[COUNT];} outputs;
448layout(location=2) uniform float gain;
449void weights(out float[COUNT] result) {
450 result = float[COUNT](1.0,2.0,3.0,4.0);
451}
452void main() {
453 uint i = gl_GlobalInvocationID.x;
454 if(i >= uint(inputs.items.length())) return;
455 float kernel[COUNT];
456 weights(kernel);
457 const float values[COUNT] = float[COUNT](0.0,1.0,2.0,3.0);
458 outputs.results[i] = inputs.items[i]*gain + kernel[i]
459 + sqrt(float(vec2(4.0,9.0))) + float(values.length());
460}
461'''
462 application = '''
463#include "shdr.h"
464#include <iostream>
465int main(int argc, char** argv) {
466 auto shader=shdr::make_kernel("import_kernel");
467 if(argc==2) {
468 const std::string name(argv[1]);
469 const shdr::profile profile=name=="es31" ? shdr::profile::es31 :
470 name=="es32" ? shdr::profile::es32 : name=="gl43" ? shdr::profile::gl43 :
471 name=="gl45" ? shdr::profile::gl45 : shdr::profile::gl46;
472 std::cout<<shader->glsl(profile);
473 return 0;
474 }
475 shdr::buffer<float> input{1.f,2.f,3.f,4.f,5.f,6.f}, output(6);
476 shader->bind("inputs.items",input).bind("outputs.results",output).set<float>("gain",2.f);
477 shdr::pipeline pipeline;
478 pipeline+=shdr::dispatch(*shader,shdr::extent(4));
479 pipeline.run();
480 const shdr::buffer<float>& read=output;
481 const float expected[]={9.f,12.f,15.f,18.f,0.f,0.f};
482 for(unsigned i=0;i<6;++i) if(read.data()[i]!=expected[i]) return 1;
483 shdr::buffer<float> short_input(3);
484 shader->bind("inputs.items",short_input);
485 try { pipeline.run(); } catch(const shdr::error&) { return 0; }
486 return 2;
487}
488'''
489 with tempfile.TemporaryDirectory(prefix="shdr-import-test-") as directory: # Keep this temporary resource alive throughout the following block.
490 path = Path(directory) # Compute path for the following operation.
491 adapter, main, executable = ( # Supply adapter for the continued expression.
492 path / "adapter.cpp", # Supply path for the continued expression.
493 path / "main.cpp", # Supply path for the continued expression.
494 path / "import", # Supply path for the continued expression.
495 ) # Finish this expression or collection.
496 adapter.write_text(COMPILER.generate(source, "import_kernel")) # Apply write text to these arguments.
497 main.write_text(application) # Apply write text to these arguments.
498 subprocess.run( # Run the compiler, validator or generated executable.
499 [ # Finish this expression or collection.
500 CPP_COMPILER, # Supply CPP COMPILER for the continued expression.
501 "-std=c++11",
502 "-pedantic-errors",
503 "-I",
504 str(ROOT), # Apply str to these arguments.
505 str(adapter), # Apply str to these arguments.
506 str(main), # Apply str to these arguments.
507 "-o",
508 str(executable), # Apply str to these arguments.
509 ], # Finish this expression or collection.
510 check=True, # Compute check for the following operation.
511 ) # Finish this expression or collection.
512 subprocess.run([str(executable)], check=True, cwd=directory) # Run the compiler, validator or generated executable.
513 if GLSLANG_VALIDATOR: # Select this branch when its condition holds.
514 for profile in ("es31", "es32", "gl43", "gl45", "gl46"): # Process each profile in this sequence.
515 exported = subprocess.run( # Compute exported for the following operation.
516 [str(executable), profile], # Apply str to these arguments.
517 check=True, # Compute check for the following operation.
518 stdout=subprocess.PIPE, # Compute stdout for the following operation.
519 text=True, # Compute text for the following operation.
520 ).stdout # Supply stdout for the continued expression.
521 shader = path / (profile + ".comp") # Compute shader for the following operation.
522 shader.write_text(exported) # Apply write text to these arguments.
523 subprocess.run( # Run the compiler, validator or generated executable.
524 [GLSLANG_VALIDATOR, "-S", "comp", str(shader)], check=True # Apply str to these arguments.
525 ) # Finish this expression or collection.
526
527 def test_imported_descriptor_defaults_and_push_constants(self): # Verify imported descriptor defaults and push constants behavior.
528 source = '''
529#version 450
530layout(constant_id=0) const uint BIAS=3u;
531layout(local_size_x_id=2) in;
532layout(set=0,binding=1) readonly buffer Input {float values[];} input_data;
533writeonly buffer Output {float results[];};
534layout(push_constant) uniform Params {float r; uint count;} p;
535void main() {
536 uint i=gl_GlobalInvocationID.x;
537 if(i<p.count) results[i]=input_data.values[i]*p.r+float(BIAS);
538}
539'''
540 application = '''
541#include "shdr.h"
542#include <iostream>
543int main(int argc,char** argv) {
544 auto shader=shdr::make_kernel("descriptor_kernel");
545 if(argc==2) {
546 const std::string name(argv[1]);
547 const shdr::profile p=name=="es31"?shdr::profile::es31:name=="es32"?shdr::profile::es32:
548 name=="gl43"?shdr::profile::gl43:name=="gl45"?shdr::profile::gl45:shdr::profile::gl46;
549 std::cout<<shader->glsl(p); return 0;
550 }
551 shdr::buffer<float> input{2.f,5.f},output(2);
552 shader->bind("input_data.values",input).bind("results",output)
553 .set<float>("p.r",4.f).set<unsigned>("p.count",2u);
554 if(shader->local_size().x!=1) return 1;
555 shdr::pipeline pipeline=shdr::dispatch(*shader,shdr::extent(2));
556 pipeline.run();
557 const shdr::buffer<float>& read=output;
558 return read.data()[0]==11.f && read.data()[1]==23.f ? 0 : 2;
559}
560'''
561 with tempfile.TemporaryDirectory(prefix="shdr-descriptor-test-") as directory: # Keep this temporary resource alive throughout the following block.
562 path = Path(directory) # Compute path for the following operation.
563 adapter, main, executable = ( # Supply adapter for the continued expression.
564 path / "adapter.cpp", # Supply path for the continued expression.
565 path / "main.cpp", # Supply path for the continued expression.
566 path / "descriptor", # Supply path for the continued expression.
567 ) # Finish this expression or collection.
568 adapter.write_text(COMPILER.generate(source, "descriptor_kernel")) # Apply write text to these arguments.
569 main.write_text(application) # Apply write text to these arguments.
570 subprocess.run( # Run the compiler, validator or generated executable.
571 [ # Finish this expression or collection.
572 CPP_COMPILER, # Supply CPP COMPILER for the continued expression.
573 "-std=c++11",
574 "-pedantic-errors",
575 "-I",
576 str(ROOT), # Apply str to these arguments.
577 str(adapter), # Apply str to these arguments.
578 str(main), # Apply str to these arguments.
579 "-o",
580 str(executable), # Apply str to these arguments.
581 ], # Finish this expression or collection.
582 check=True, # Compute check for the following operation.
583 ) # Finish this expression or collection.
584 subprocess.run([str(executable)], check=True, cwd=directory) # Run the compiler, validator or generated executable.
585 if GLSLANG_VALIDATOR: # Select this branch when its condition holds.
586 for profile in ("es31", "es32", "gl43", "gl45", "gl46"): # Process each profile in this sequence.
587 shader = path / (profile + ".comp") # Compute shader for the following operation.
588 exported = subprocess.run( # Compute exported for the following operation.
589 [str(executable), profile], # Apply str to these arguments.
590 check=True, # Compute check for the following operation.
591 stdout=subprocess.PIPE, # Compute stdout for the following operation.
592 text=True, # Compute text for the following operation.
593 ).stdout # Supply stdout for the continued expression.
594 shader.write_text(exported) # Apply write text to these arguments.
595 subprocess.run( # Run the compiler, validator or generated executable.
596 [GLSLANG_VALIDATOR, "-S", "comp", str(shader)], check=True # Apply str to these arguments.
597 ) # Finish this expression or collection.
598 _, gpu, local = COMPILER.transform("void main(){}", "default_kernel") # Apply transform to these arguments.
599 self.assertEqual(local, [1, 1, 1]) # Check the expected frontend result or execution status.
600 self.assertIn("local_size_x=1,local_size_y=1,local_size_z=1", gpu) # Check the expected frontend result or execution status.
601
602 def test_helper_constant_shadow_does_not_change_storage_layout(self): # Verify storage layout uses the enclosing constant scope.
603 source = '''
604const uint WIDTH=4u;
605layout(local_size_x=WIDTH) in;
606layout(std430,binding=0) writeonly buffer Output {float values[WIDTH];};
607float helper(){
608 const uint WIDTH=2u;
609 float items[WIDTH];
610 const vec2 first[WIDTH]=vec2[WIDTH](vec2(1.0,2.0),vec2(3.0,4.0)),
611 second[WIDTH]=vec2[WIDTH](vec2(5.0,6.0),vec2(7.0,8.0));
612 float a[WIDTH]=float[WIDTH](1.0,2.0), scalar=3.0, b[WIDTH]=float[WIDTH](4.0,5.0);
613 float result=first[1].y+second[0].x+float(items.length())+a[1]+b[0]+scalar;
614 for(const uint WIDTH=3u;WIDTH==3u;){float temporary[WIDTH];result+=float(temporary.length());break;}
615 float after_loop[WIDTH];
616 return result+float(after_loop.length());
617}
618void main(){
619 for(const uint WIDTH=2u;WIDTH==2u;){float temporary[WIDTH];if(temporary.length()!=2)return;break;}
620 float after_loop[WIDTH];
621 values[gl_GlobalInvocationID.x]=float(values.length())+helper()+float(after_loop.length());
622}
623'''
624 _, _, local = COMPILER.transform(source, "shadow_kernel") # Apply transform to these arguments.
625 self.assertEqual(local, [4, 1, 1]) # Check the expected frontend result or execution status.
626 application = '''
627#include "shdr.h"
628int main() {
629 shdr::buffer<float> output(4);
630 auto shader=shdr::make_kernel("shadow_kernel");
631 shader->bind("values",output);
632 shdr::pipeline pipeline=shdr::dispatch(*shader,shdr::extent(1));
633 pipeline.run();
634 const shdr::buffer<float>& read=output;
635 for(unsigned i=0;i<4;++i) if(read.data()[i]!=33.f) return 1;
636}
637'''
638 with tempfile.TemporaryDirectory(prefix="shdr-constant-scope-") as directory: # Keep this temporary resource alive throughout the following block.
639 path = Path(directory) # Compute path for the following operation.
640 adapter, main, executable = ( # Supply adapter for the continued expression.
641 path / "adapter.cpp", # Supply path for the continued expression.
642 path / "main.cpp", # Supply path for the continued expression.
643 path / "shadow", # Supply path for the continued expression.
644 ) # Finish this expression or collection.
645 adapter.write_text(COMPILER.generate(source, "shadow_kernel")) # Apply write text to these arguments.
646 main.write_text(application) # Apply write text to these arguments.
647 subprocess.run( # Run the compiler, validator or generated executable.
648 [ # Finish this expression or collection.
649 CPP_COMPILER, # Supply CPP COMPILER for the continued expression.
650 "-std=c++11",
651 "-pedantic-errors",
652 "-I",
653 str(ROOT), # Apply str to these arguments.
654 str(adapter), # Apply str to these arguments.
655 str(main), # Apply str to these arguments.
656 "-o",
657 str(executable), # Apply str to these arguments.
658 ], # Finish this expression or collection.
659 check=True, # Compute check for the following operation.
660 ) # Finish this expression or collection.
661 subprocess.run([str(executable)], check=True, cwd=directory) # Run the compiler, validator or generated executable.
662
663
664if __name__ == "__main__": # Select this branch when its condition holds.
665 arguments = argparse.ArgumentParser(description=__doc__) # Compute arguments for the following operation.
666 arguments.add_argument( # Apply add argument to these arguments.
667 "--cxx", required=True, help="C++11 compiler used by the library build" # Supply cxx for the continued expression.
668 ) # Finish this expression or collection.
669 arguments.add_argument( # Apply add argument to these arguments.
670 "--glslang", help="optional GLSL validator for all supported import profiles" # Supply glslang for the continued expression.
671 ) # Finish this expression or collection.
672 arguments.add_argument( # Apply add argument to these arguments.
673 "--egl",
674 action="store_true", # Compute action for the following operation.
675 help="check portable values on an optional headless EGL backend", # Compute help for the following operation.
676 ) # Finish this expression or collection.
677 options, unittest_arguments = arguments.parse_known_args() # Apply parse known args to these arguments.
678 if options.egl: # Select this branch when its condition holds.
679 import shlex # Import shlex for this operation.
680
681 EGL_FLAGS = shlex.split( # Compute EGL FLAGS for the following operation.
682 subprocess.run( # Run the compiler, validator or generated executable.
683 ["pkg-config", "--cflags", "--libs", "egl"], # Supply pkg for the continued expression.
684 check=True, # Compute check for the following operation.
685 stdout=subprocess.PIPE, # Compute stdout for the following operation.
686 text=True, # Compute text for the following operation.
687 ).stdout # Supply stdout for the continued expression.
688 ) # Finish this expression or collection.
689 CPP_COMPILER = options.cxx # Compute CPP COMPILER for the following operation.
690 GLSLANG_VALIDATOR = options.glslang # Compute GLSLANG VALIDATOR for the following operation.
691 unittest.main(argv=[__file__] + unittest_arguments) # Apply main to these arguments.