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.