Source browser

tests/test_gpu.cpp

Read the complete file below or download its original bytes. Line numbers provide stable links to a declaration or processing step.

File fingerprint
1#include "examples/runner.hpp" // Include examples/runner.hpp declarations.
2#include <atomic> // Include atomic declarations.
3#include <chrono> // Include chrono declarations.
4#include <cstddef> // Include cstddef declarations.
5#include <thread> // Include thread declarations.
6
7namespace { // Group these helpers within their namespace.
8#if defined(_WIN32) // Select the platform or build feature required by this branch.
9#define SHDR_TEST_GL_CALL __stdcall // Define SHDR_TEST_GL_CALL for the declarations that follow.
10#else // Select the alternative implementation for this build.
11#define SHDR_TEST_GL_CALL // Define SHDR_TEST_GL_CALL for the declarations that follow.
12#endif // Finish this conditional compilation branch.
13
14void require(bool value, const char* message) { // Check that this operation satisfies the expected behavior.
15    if (!value) // Check this condition before entering the following branch.
16        throw std::runtime_error(message); // Report this failed operation.
17} // End this block.
18
19template <class Function> void must_throw(Function function, const char* message) { // Accept the value types or dimensions used by this template.
20    bool caught = false; // Set caught from this expression.
21    try { // Run the operation while retaining the error handling below.
22        function(); // Apply function to these arguments.
23    } catch (const shdr::error&) { // Report the exception from the preceding operation.
24        caught = true; // Set caught from this expression.
25    } // End this block.
26    require(caught, message); // Check that this operation satisfies the expected behavior.
27} // End this block.
28
29// Count real driver synchronization and readback calls during submission polling.
30// Check zero-timeout waits and the resources retained by pending work.
31struct gl_calls { // Declare gl calls.
32    shdr::detail::gl_api& gl; // Declare gl for the following calculation.
33    shdr::detail::gl_api::Finish_type finish; // Declare finish for the following calculation.
34    shdr::detail::gl_api::MapBufferRange_type map; // Declare map for the following calculation.
35    shdr::detail::gl_api::ClientWaitSync_type wait; // Declare wait for the following calculation.
36    shdr::detail::gl_api::DispatchCompute_type dispatch; // Declare dispatch for the following calculation.
37    std::atomic<unsigned> finishes, maps, waits, blocking_waits, dispatches; // Declare finishes for the following calculation.
38    static gl_calls*& active() { // Declare gl calls for the following calculation.
39        static gl_calls* value = 0; // Set value from this expression.
40        return value; // Return value.
41    } // End this block.
42    static void SHDR_TEST_GL_CALL observe_finish() { // Declare SHDR TEST GL CALL for the following calculation.
43        ++active()->finishes; // Apply active to these arguments.
44        active()->finish(); // Apply active to these arguments.
45    } // End this block.
46    static void* SHDR_TEST_GL_CALL observe_map(shdr::detail::GLenum target, // Declare SHDR TEST GL CALL for the following calculation.
47                                               shdr::detail::GLintptr offset, // Declare offset for the following calculation.
48                                               shdr::detail::GLsizeiptr size, // Declare size for the following calculation.
49                                               shdr::detail::GLbitfield access) { // Declare access for the following calculation.
50        ++active()->maps; // Apply active to these arguments.
51        return active()->map(target, offset, size, access); // Return active()->map(target, offset, size, access).
52    } // End this block.
53    static shdr::detail::GLenum SHDR_TEST_GL_CALL observe_wait(shdr::detail::GLsync fence, // Declare SHDR TEST GL CALL for the following calculation.
54                                                               shdr::detail::GLbitfield flags, // Declare flags for the following calculation.
55                                                               shdr::detail::GLuint64 timeout) { // Declare timeout for the following calculation.
56        ++active()->waits; // Apply active to these arguments.
57        if (timeout) // Check this condition before entering the following branch.
58            ++active()->blocking_waits; // Apply active to these arguments.
59        return active()->wait(fence, flags, timeout); // Return active()->wait(fence, flags, timeout).
60    } // End this block.
61    static void SHDR_TEST_GL_CALL observe_dispatch(shdr::detail::GLuint x, // Declare SHDR TEST GL CALL for the following calculation.
62                                                   shdr::detail::GLuint y, // Declare y for the following calculation.
63                                                   shdr::detail::GLuint z) { // Declare z for the following calculation.
64        ++active()->dispatches; // Apply active to these arguments.
65        active()->dispatch(x, y, z); // Apply active to these arguments.
66    } // End this block.
67    explicit gl_calls(shdr::device& device) // Declare gl calls with the parameters continued below.
68        : gl(device.state()->gl), finish(gl.Finish), map(gl.MapBufferRange), // Declare gl with the parameters continued below.
69          wait(gl.ClientWaitSync), dispatch(gl.DispatchCompute), finishes(0), maps(0), waits(0), // Apply wait to these arguments.
70          blocking_waits(0), dispatches(0) { // Apply blocking waits to these arguments.
71        require(active() == 0, "nested driver observer"); // Check that this operation satisfies the expected behavior.
72        active() = this; // Apply active to these arguments.
73        gl.Finish = observe_finish; // Set gl.Finish from this expression.
74        gl.MapBufferRange = observe_map; // Set gl.Map Buffer Range from this expression.
75        gl.ClientWaitSync = observe_wait; // Set gl.Client Wait Sync from this expression.
76        gl.DispatchCompute = observe_dispatch; // Set gl.Dispatch Compute from this expression.
77    } // End this block.
78    ~gl_calls() { // Apply gl calls to these arguments.
79        gl.Finish = finish; // Set gl.Finish from this expression.
80        gl.MapBufferRange = map; // Set gl.Map Buffer Range from this expression.
81        gl.ClientWaitSync = wait; // Set gl.Client Wait Sync from this expression.
82        gl.DispatchCompute = dispatch; // Set gl.Dispatch Compute from this expression.
83        active() = 0; // Apply active to these arguments.
84    } // End this block.
85}; // Finish this declaration.
86#undef SHDR_TEST_GL_CALL // Continue the expression using undef, SHDR TEST GL CALL.
87
88template <class Function> Function resolve(const char* name) { // Accept the value types or dimensions used by this template.
89    void* address = shdr_examples::egl_context::resolve(name); // Set address from this expression.
90    if (!address) // Check this condition before entering the following branch.
91        throw std::runtime_error(std::string("missing GL entry point ") + name); // Report missing GL entry point .
92    return reinterpret_cast<Function>(address); // Return reinterpret cast<Function>(address).
93} // End this block.
94
95/// An application's independently owned SSBO, borrowed temporarily by shdr.
96class raw_buffer { // Declare raw buffer.
97    typedef void (*gen_fn)(int, unsigned*); // Construct void with these initial values.
98    typedef void (*bind_fn)(unsigned, unsigned); // Construct void with these initial values.
99    typedef void (*data_fn)(unsigned, std::ptrdiff_t, const void*, unsigned); // Construct void with these initial values.
100    typedef void (*delete_fn)(int, const unsigned*); // Construct void with these initial values.
101    unsigned name_; // Declare name for the following calculation.
102    delete_fn destroy_; // Declare destroy for the following calculation.
103
104  public: // Set public access for the following class members.
105    raw_buffer(const float* values, std::size_t count) // Apply raw buffer to these arguments.
106        : name_(0), destroy_(resolve<delete_fn>("glDeleteBuffers")) { // Implement name for the operations below.
107        resolve<gen_fn>("glGenBuffers")(1, &name_); // Provide this text argument for the operation above.
108        resolve<bind_fn>("glBindBuffer")(0x90D2, name_); // GL_SHADER_STORAGE_BUFFER
109        resolve<data_fn>("glBufferData")(0x90D2, // Provide this text argument for the operation above.
110                                         std::ptrdiff_t(count * sizeof(float)), // Apply ptrdiff t to these arguments.
111                                         values, // Continue the expression using values.
112                                         0x88E8); // GL_DYNAMIC_DRAW
113        typedef unsigned (*error_fn)(); // Construct unsigned with these initial values.
114        if (resolve<error_fn>("glGetError")() != 0) { // Check this condition before entering the following branch.
115            destroy_(1, &name_); // Apply destroy to these arguments.
116            throw std::runtime_error("failed to allocate application's SSBO"); // Report failed to allocate application's SSBO.
117        } // End this block.
118    } // End this block.
119    ~raw_buffer() { // Apply raw buffer to these arguments.
120        destroy_(1, &name_); // Apply destroy to these arguments.
121    } // End this block.
122    unsigned name() const { // Implement name for the operations below.
123        return name_; // Return name.
124    } // End this block.
125    void set_first(float value) { // Implement set first for the operations below.
126        typedef void (*subdata_fn)(unsigned, std::ptrdiff_t, std::ptrdiff_t, const void*); // Construct void with these initial values.
127        resolve<bind_fn>("glBindBuffer")(0x90D2, name_); // Provide this text argument for the operation above.
128        resolve<subdata_fn>("glBufferSubData")(0x90D2, 0, sizeof(float), &value); // Apply sizeof to these arguments.
129    } // End this block.
130    raw_buffer(const raw_buffer&) = delete; // Apply raw buffer to these arguments.
131    raw_buffer& operator=(const raw_buffer&) = delete; // Set operator from this expression.
132}; // Finish this declaration.
133
134void test_external_buffers(shdr::device& device) { // Implement test external buffers for the operations below.
135    const unsigned count = 33; // Set count from this expression.
136    float values[count], zero[count] = {}; // Set zero[count] from this expression.
137    for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
138        values[i] = float(i) - 8.0f; // Set values[i] from this expression.
139    raw_buffer application_input(values, count), application_output(zero, count); // Construct application input with these initial values.
140    { // Begin this local scope.
141        shdr::buffer<float> input = // Set input from this expression.
142            shdr::buffer<float>::from_gl(device, application_input.name(), count); // Apply from gl to these arguments.
143        shdr::buffer<float> output = // Set output from this expression.
144            shdr::buffer<float>::from_gl(device, application_output.name(), count); // Apply from gl to these arguments.
145        if (input.gl_handle(device) != application_input.name() || // Check this condition before entering the following branch.
146            output.gl_handle(device) != application_output.name()) // Apply gl handle to these arguments.
147            throw std::runtime_error("borrowing changed application's GL handles"); // Report borrowing changed application's GL handles.
148        auto affine_owner = shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
149        shdr::kernel& affine = *affine_owner; // Set affine from this expression.
150        affine.bind("input_values", input); // Connect this shader port to its application resource.
151        affine.bind("output_values", output); // Connect this shader port to its application resource.
152        affine.set<unsigned>("count", count); // Supply this shader uniform with its typed application value.
153        affine.set<float>("scale", 2.0f); // Supply this shader uniform with its typed application value.
154        affine.set<float>("offset", 1.0f); // Supply this shader uniform with its typed application value.
155
156        shdr::pipeline pipeline; // Declare pipeline for the following calculation.
157        pipeline += shdr::dispatch(affine, shdr::extent(1)); // Add a shader dispatch with the required workgroup counts.
158        pipeline.run(device); // Execute the configured pipeline on the requested backend.
159        const shdr::buffer<float>& read_output = output; // Use a constant buffer view so GPU writes synchronize before host reads.
160        for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
161            shdr_examples::require_close( // Check the computed value against the independent reference.
162                read_output.data()[i], values[i] * 2 + 1, "external GPU buffer"); // Apply data to these arguments.
163
164        input.data()[0] = 123.0f; // Apply data to these arguments.
165        pipeline.run(device); // Execute the configured pipeline on the requested backend.
166        shdr_examples::require_close( // Check the computed value against the independent reference.
167            read_output.data()[0], 247.0f, "host edit uploaded to external buffer"); // Apply data to these arguments.
168        // The CPU version can use those same externally owned resources dynamically.
169        affine.set<float>("scale", 3.0f); // Supply this shader uniform with its typed application value.
170        pipeline.run(); // Execute the configured pipeline on the requested backend.
171        shdr_examples::require_close( // Check the computed value against the independent reference.
172            read_output.data()[0], 370.0f, "CPU dispatch using borrowed GL resource"); // Apply data to these arguments.
173        output.upload(device); // Apply upload to these arguments.
174        application_output.set_first(321.0f); // Apply set first to these arguments.
175        output.mark_gpu_modified(device); // Apply mark gpu modified to these arguments.
176        shdr_examples::require_close(read_output.data()[0], 321.0f, "application GL edit readback"); // Check the computed value against the independent reference.
177
178        float* retained = input.data(); // Set retained from this expression.
179        input.upload(device); // Apply upload to these arguments.
180        retained[1] = 80.0f; // Set retained[1] from this expression.
181        input.mark_host_modified(); // Apply mark host modified to these arguments.
182        pipeline.run(device); // Execute the configured pipeline on the requested backend.
183        shdr_examples::require_close( // Check the computed value against the independent reference.
184            read_output.data()[1], 241.0f, "explicit retained host pointer dirty mark"); // Apply data to these arguments.
185    } // End this block.
186    typedef unsigned char (*is_buffer_fn)(unsigned); // Declare unsigned for the following calculation.
187    is_buffer_fn is_buffer = resolve<is_buffer_fn>("glIsBuffer"); // Set is buffer from this expression.
188    if (!is_buffer(application_input.name()) || !is_buffer(application_output.name())) // Check this condition before entering the following branch.
189        throw std::runtime_error("shdr destroyed an application's borrowed SSBO"); // Report shdr destroyed an application's borrowed SSBO.
190} // End this block.
191
192void test_shader_errors(shdr::device& device) { // Implement test shader errors for the operations below.
193    // Dedicated test subclasses supply malformed programs for compile-failure checks.
194    struct missing_kernel : shdr::kernel { // Declare missing kernel.
195        missing_kernel() : shdr::kernel("missing", shdr::extent(1)) { // Apply missing kernel to these arguments.
196        } // End this block.
197        void main() override { // Run this example and validate its results.
198        } // End this block.
199    } missing; // Continue the expression using missing.
200    struct broken_kernel : shdr::kernel { // Declare broken kernel.
201        broken_kernel() : shdr::kernel("broken", shdr::extent(1)) { // Apply broken kernel to these arguments.
202            set_embedded_source("layout(local_size_x=1) in; void main(){invalid_glsl;}\n"); // Apply set embedded source to these arguments.
203        } // End this block.
204        void main() override { // Run this example and validate its results.
205        } // End this block.
206    } broken; // Continue the expression using broken.
207    shdr::pipeline pipeline; // Declare pipeline for the following calculation.
208    pipeline += shdr::dispatch(missing, shdr::extent(1)); // Add a shader dispatch with the required workgroup counts.
209    bool failed = false; // Set failed from this expression.
210    try { // Run the operation while retaining the error handling below.
211        pipeline.run(device); // Execute the configured pipeline on the requested backend.
212    } catch (const shdr::error&) { // Report the exception from the preceding operation.
213        failed = true; // Set failed from this expression.
214    } // End this block.
215    if (!failed) // Check this condition before entering the following branch.
216        throw std::runtime_error("missing GPU shader source was accepted"); // Report missing GPU shader source was accepted.
217    shdr::pipeline invalid; // Declare invalid for the following calculation.
218    invalid += shdr::dispatch(broken, shdr::extent(1)); // Add a shader dispatch with the required workgroup counts.
219    failed = false; // Set failed from this expression.
220    try { // Run the operation while retaining the error handling below.
221        invalid.run(device); // Execute the configured pipeline on the requested backend.
222    } catch (const shdr::error&) { // Report the exception from the preceding operation.
223        failed = true; // Set failed from this expression.
224    } // End this block.
225    if (!failed) // Check this condition before entering the following branch.
226        throw std::runtime_error("invalid GPU shader source was accepted"); // Report invalid GPU shader source was accepted.
227
228    shdr::buffer<float> input(4), output(4); // Construct input with these initial values.
229    auto shader = shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
230    shader->bind("input_values", input) // Connect this shader port to its application resource.
231        .bind("output_values", output) // Connect this shader port to its application resource.
232        .set<unsigned>("count", 4u) // Supply this shader uniform with its typed application value.
233        .set<float>("scale", 1.0f) // Supply this shader uniform with its typed application value.
234        .set<float>("offset", 2.0f); // Supply this shader uniform with its typed application value.
235    shdr::pipeline recovery; // Declare recovery for the following calculation.
236    recovery += shdr::dispatch(*shader, shdr::extent(1)); // Add a shader dispatch with the required workgroup counts.
237    recovery.run(device); // Verify successful dispatch on the same device after the compile failure.
238    const shdr::buffer<float>& read_output = output; // Use a constant buffer view so GPU writes synchronize before host reads.
239    for (unsigned i = 0; i < 4; ++i) // Iterate over i for this calculation.
240        shdr_examples::require_close( // Check the computed value against the independent reference.
241            read_output.data()[i], 2.0f, "shader recovery after compile error"); // Apply data to these arguments.
242} // End this block.
243
244void test_typed_images(shdr::device& device) { // Implement test typed images for the operations below.
245    const unsigned width = 5, height = 3, pixels = width * height; // Set width from this expression.
246    shdr::image2d<float, 1> f(width, height); // Construct f with these initial values.
247    shdr::image2d<int, 1> i(width, height); // Construct i with these initial values.
248    shdr::image2d<unsigned, 1> u(width, height); // Construct u with these initial values.
249    shdr::image2d<float, 4> f4(width, height); // Construct f4 with these initial values.
250    shdr::image2d<int, 4> i4(width, height); // Construct i4 with these initial values.
251    shdr::image2d<unsigned, 4> u4(width, height); // Construct u4 with these initial values.
252    for (unsigned p = 0; p < pixels; ++p) { // Iterate over p for this calculation.
253        f.data()[p] = float(p); // Apply data to these arguments.
254        i.data()[p] = int(p); // Apply data to these arguments.
255        u.data()[p] = p; // Apply data to these arguments.
256        for (unsigned c = 0; c < 4; ++c) { // Iterate over c for this calculation.
257            f4.data()[4 * p + c] = float(4 * p + c); // Apply data to these arguments.
258            i4.data()[4 * p + c] = int(4 * p + c); // Apply data to these arguments.
259            u4.data()[4 * p + c] = 4 * p + c; // Apply data to these arguments.
260        } // End this block.
261    } // End this block.
262    // Borrow a texture owned by another resource wrapper to exercise from_gl.
263    shdr::image2d<float, 1> borrowed_f = // Set borrowed f from this expression.
264        shdr::image2d<float, 1>::from_gl(device, f.gl_handle(device), width, height); // Apply from gl to these arguments.
265    auto scalar_owner = shdr::make_kernel("scalar_images_kernel"); // Create the linked scalar images kernel instance.
266    shdr::kernel& scalar = *scalar_owner; // Set scalar from this expression.
267    scalar.bind("float_scalar", borrowed_f); // Connect this shader port to its application resource.
268    scalar.bind("int_scalar", i); // Connect this shader port to its application resource.
269    scalar.bind("uint_scalar", u); // Connect this shader port to its application resource.
270
271    // Input and output may alias here because each invocation handles its own pixel.
272    auto rgba_f_owner = shdr::make_kernel("rgba_float_image_kernel"); // Create the linked rgba float image kernel instance.
273    shdr::kernel& rgba_f = *rgba_f_owner; // Set rgba f from this expression.
274    rgba_f.bind("input_image", f4); // Connect this shader port to its application resource.
275    rgba_f.bind("output_image", f4); // Connect this shader port to its application resource.
276
277    auto rgba_i_owner = shdr::make_kernel("rgba_int_image_kernel"); // Create the linked rgba int image kernel instance.
278    shdr::kernel& rgba_i = *rgba_i_owner; // Set rgba i from this expression.
279    rgba_i.bind("input_image", i4); // Connect this shader port to its application resource.
280    rgba_i.bind("output_image", i4); // Connect this shader port to its application resource.
281
282    auto rgba_u_owner = shdr::make_kernel("rgba_uint_image_kernel"); // Create the linked rgba uint image kernel instance.
283    shdr::kernel& rgba_u = *rgba_u_owner; // Set rgba u from this expression.
284    rgba_u.bind("input_image", u4); // Connect this shader port to its application resource.
285    rgba_u.bind("output_image", u4); // Connect this shader port to its application resource.
286
287    shdr::pipeline pipeline = // Set pipeline from this expression.
288        shdr::dispatch(scalar, shdr::extent(2, 1)) >> shdr::dispatch(rgba_f, shdr::extent(2, 1)) >> // Add a shader dispatch with the required workgroup counts.
289        shdr::dispatch(rgba_i, shdr::extent(2, 1)) >> shdr::dispatch(rgba_u, shdr::extent(2, 1)); // Add a shader dispatch with the required workgroup counts.
290    pipeline.run(device); // Execute the configured pipeline on the requested backend.
291    f.mark_gpu_modified(device); // The application informs the original wrapper of alias writes.
292    const shdr::image2d<float, 1>& rf = f; // Set rf from this expression.
293    const shdr::image2d<int, 1>& ri = i; // Set ri from this expression.
294    const shdr::image2d<unsigned, 1>& ru = u; // Set ru from this expression.
295    const shdr::image2d<float, 4>& rf4 = f4; // Set rf4 from this expression.
296    const shdr::image2d<int, 4>& ri4 = i4; // Set ri4 from this expression.
297    const shdr::image2d<unsigned, 4>& ru4 = u4; // Set ru4 from this expression.
298    for (unsigned p = 0; p < pixels; ++p) { // Iterate over p for this calculation.
299        shdr_examples::require_close(rf.data()[p], float(p) + 0.5f, "r32f image readback"); // Check the computed value against the independent reference.
300        if (ri.data()[p] != int(p) - 2 || ru.data()[p] != p + 3) // Check this condition before entering the following branch.
301            throw std::runtime_error("integer scalar image readback"); // Report integer scalar image readback.
302        for (unsigned c = 0; c < 4; ++c) { // Iterate over c for this calculation.
303            shdr_examples::require_close(rf4.data()[4 * p + c], // Check the computed value against the independent reference.
304                                         float(4 * p + c) + float(c + 1) / 4, // Apply float to these arguments.
305                                         "rgba32f image readback"); // Provide this text argument for the operation above.
306            if (ri4.data()[4 * p + c] != int(4 * p + c) - int(c + 1) || // Check this condition before entering the following branch.
307                ru4.data()[4 * p + c] != 4 * p + c + c + 1) // Apply data to these arguments.
308                throw std::runtime_error("integer RGBA image readback"); // Report integer RGBA image readback.
309        } // End this block.
310    } // End this block.
311} // End this block.
312
313/// Resource ownership retains a headless context after its device wrapper is released.
314void test_headless_context_lifetimes(shdr::device& original) { // Implement test headless context lifetimes for the operations below.
315    std::unique_ptr<shdr::headless_device> first( // Declare first with the parameters continued below.
316        new shdr::headless_device(original.shader_profile())); // Declare shdr for the following calculation.
317    std::unique_ptr<shdr::buffer<float>> retained(new shdr::buffer<float>{3.0f, 7.0f}); // Construct retained with these initial values.
318    retained->upload(*first); // Apply upload to these arguments.
319    retained->mark_gpu_modified(*first); // Apply mark gpu modified to these arguments.
320
321    shdr::headless_device second(original.shader_profile()); // Construct second with these initial values.
322    shdr::buffer<float> input{2.0f, 4.0f}, output(2); // Declare input for the following calculation.
323    auto shader = shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
324    shader->bind("input_values", input) // Connect this shader port to its application resource.
325        .bind("output_values", output) // Connect this shader port to its application resource.
326        .set<unsigned>("count", 2u) // Supply this shader uniform with its typed application value.
327        .set<float>("scale", 3.0f) // Supply this shader uniform with its typed application value.
328        .set<float>("offset", 1.0f); // Supply this shader uniform with its typed application value.
329    shdr::pipeline pipeline = shdr::dispatch(*shader, shdr::extent(1)); // Add a shader dispatch with the required workgroup counts.
330    pipeline.run(second); // Execute the configured pipeline on the requested backend.
331    first.reset(); // Apply reset to these arguments.
332
333    const shdr::buffer<float>& read_retained = *retained; // Use a constant buffer view so GPU writes synchronize before host reads.
334    shdr_examples::require_close(read_retained.data()[0], 3.0f, "retained context readback"); // Check the computed value against the independent reference.
335    shdr_examples::require_close(read_retained.data()[1], 7.0f, "retained context readback"); // Check the computed value against the independent reference.
336    retained.reset(); // Apply reset to these arguments.
337    second.synchronize(); // Wait for submitted GPU work before continuing.
338    if (second.renderer().empty()) // Check this condition before entering the following branch.
339        throw std::runtime_error("second headless context lost its renderer"); // Report second headless context lost its renderer.
340    pipeline.run(second); // Execute the configured pipeline on the requested backend.
341    const shdr::buffer<float>& read_output = output; // Use a constant buffer view so GPU writes synchronize before host reads.
342    shdr_examples::require_close(read_output.data()[0], 7.0f, "second context remains usable"); // Check the computed value against the independent reference.
343    shdr_examples::require_close(read_output.data()[1], 13.0f, "second context remains usable"); // Check the computed value against the independent reference.
344    original.synchronize(); // Wait for submitted GPU work before continuing.
345} // End this block.
346
347void configure_affine(shdr::kernel& shader, // Declare configure affine with the parameters continued below.
348                      const shdr::buffer<float>& input, // Declare input for the following calculation.
349                      const shdr::buffer<float>& output, // Declare output for the following calculation.
350                      unsigned count) { // Declare count for the following calculation.
351    shader.bind("input_values", input) // Connect this shader port to its application resource.
352        .bind("output_values", output) // Connect this shader port to its application resource.
353        .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
354        .set<float>("scale", 2.0f) // Supply this shader uniform with its typed application value.
355        .set<float>("offset", 1.0f); // Supply this shader uniform with its typed application value.
356} // End this block.
357
358void test_gpu_submissions(shdr::device& device) { // Implement test gpu submissions for the operations below.
359    const unsigned count = 257; // Set count from this expression.
360    shdr::buffer<float> input(count), output(count); // Construct input with these initial values.
361    for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
362        input.data()[i] = float(i); // Apply data to these arguments.
363    auto shader = shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
364    configure_affine(*shader, input, output, count); // Apply configure affine to these arguments.
365    shdr::pipeline pipeline(shdr::dispatch(*shader, shdr::extent(5))); // Add a shader dispatch with the required workgroup counts.
366    pipeline.prepare(device); // Apply prepare to these arguments.
367    gl_calls observed(device); // Construct observed with these initial values.
368    shdr::submission submitted = pipeline.submit(device); // Submit work and retain its completion handle.
369    require(submitted.valid(), "nonempty GPU submission has no completion handle"); // Check that this operation satisfies the expected behavior.
370    require(observed.dispatches == 1 && observed.finishes == 0 && observed.maps == 0, // Check that this operation satisfies the expected behavior.
371            "GPU submission performed an implicit finish or readback"); // Provide this text argument for the operation above.
372    const shdr::wait_status zero = submitted.wait(std::chrono::nanoseconds(0)); // Set zero from this expression.
373    require(zero == shdr::wait_status::ready || zero == shdr::wait_status::timeout, // Check that this operation satisfies the expected behavior.
374            "zero timeout status"); // Provide this text argument for the operation above.
375    require(observed.blocking_waits == 0, "zero timeout performed a blocking driver wait"); // Check that this operation satisfies the expected behavior.
376    must_throw( // Check that this operation satisfies the expected behavior.
377        [&] { // Complete this expression or declaration.
378            submitted.wait(std::chrono::nanoseconds(-1)); // Apply wait to these arguments.
379        }, // Finish this declaration.
380        "negative timeout accepted"); // Provide this text argument for the operation above.
381    const unsigned queries = observed.waits; // Set queries from this expression.
382    std::atomic<bool> wrong_thread_rejected(false); // Construct wrong thread rejected with these initial values.
383    std::thread wrong_thread([&] { // Implement wrong thread for the operations below.
384        try { // Run the operation while retaining the error handling below.
385            submitted.ready(); // Apply ready to these arguments.
386        } catch (const shdr::error&) { // Report the exception from the preceding operation.
387            wrong_thread_rejected = true; // Set wrong thread rejected from this expression.
388        } // End this block.
389    }); // Finish this declaration.
390    wrong_thread.join(); // Apply join to these arguments.
391    require(wrong_thread_rejected && observed.waits == queries, // Check that this operation satisfies the expected behavior.
392            "completion query used GL on the wrong thread"); // Provide this text argument for the operation above.
393
394    shdr::executor workers(1, 2); // Construct workers with these initial values.
395    must_throw( // Check that this operation satisfies the expected behavior.
396        [&] { // Complete this expression or declaration.
397            workers.submit(pipeline); // Submit work and retain its completion handle.
398        }, // Finish this declaration.
399        "CPU executor accepted newer GPU data"); // Provide this text argument for the operation above.
400    require(observed.maps == 0, "CPU submission rejection downloaded GPU data"); // Check that this operation satisfies the expected behavior.
401    shdr::pipeline mixed = shdr::dispatch(*shader, shdr::extent(5)) >> // Add a shader dispatch with the required workgroup counts.
402                           shdr::dispatch(*shader, shdr::extent(5), shdr::backend::cpu); // Add a shader dispatch with the required workgroup counts.
403    must_throw( // Check that this operation satisfies the expected behavior.
404        [&] { // Complete this expression or declaration.
405            mixed.submit(device); // Submit work and retain its completion handle.
406        }, // Finish this declaration.
407        "GPU submission accepted a CPU stage"); // Provide this text argument for the operation above.
408    require(observed.dispatches == 1, "mixed submission dispatched before rejecting its CPU stage"); // Check that this operation satisfies the expected behavior.
409
410    shdr::submission moved = std::move(submitted); // Set moved from this expression.
411    require(!submitted.valid() && submitted.ready() && moved.valid(), // Check that this operation satisfies the expected behavior.
412            "GPU completion move semantics"); // Provide this text argument for the operation above.
413    const shdr::wait_status bounded = moved.wait(std::chrono::milliseconds(5)); // Set bounded from this expression.
414    require(bounded == shdr::wait_status::ready || bounded == shdr::wait_status::timeout, // Check that this operation satisfies the expected behavior.
415            "bounded completion wait status"); // Provide this text argument for the operation above.
416    moved.wait(); // Blocking is explicit at this call.
417    const shdr::buffer<float>& result = output; // Use a constant buffer view so GPU writes synchronize before host reads.
418    for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
419        shdr_examples::require_close(result.data()[i], float(i) * 2 + 1, "async GPU result"); // Check the computed value against the independent reference.
420    workers.submit(pipeline).get(); // Host residency permits a CPU snapshot after readback.
421    for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
422        shdr_examples::require_close( // Check the computed value against the independent reference.
423            result.data()[i], float(i) * 2 + 1, "async CPU after explicit readback"); // Apply data to these arguments.
424    shdr::submission empty = shdr::pipeline().submit(device); // Submit work and retain its completion handle.
425    require(!empty.valid() && empty.ready(), "empty pipeline issued GPU work"); // Check that this operation satisfies the expected behavior.
426} // End this block.
427
428void test_dropped_submissions(shdr::device& device) { // Implement test dropped submissions for the operations below.
429    std::weak_ptr<shdr::detail::resource_state> input_lifetime, output_lifetime; // Declare input lifetime for the following calculation.
430    std::weak_ptr<shdr::detail::program_state> program_lifetime; // Declare program lifetime for the following calculation.
431    shdr::submission completion; // Declare completion for the following calculation.
432    gl_calls observed(device); // Construct observed with these initial values.
433    { // Begin this local scope.
434        shdr::buffer<float> input{2.0f, 4.0f}, output(2); // Declare input for the following calculation.
435        auto shader = shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
436        configure_affine(*shader, input, output, 2); // Apply configure affine to these arguments.
437        shdr::pipeline pipeline(shdr::dispatch(*shader, shdr::extent(1))); // Add a shader dispatch with the required workgroup counts.
438        completion = pipeline.submit(device); // Submit work and retain its completion handle.
439        input_lifetime = input.state(); // Set input lifetime from this expression.
440        output_lifetime = output.state(); // Set output lifetime from this expression.
441        program_lifetime = shader->program_state(); // Set program lifetime from this expression.
442    } // End this block.
443    require(!input_lifetime.expired() && !output_lifetime.expired() && !program_lifetime.expired(), // Check that this operation satisfies the expected behavior.
444            "GPU submission did not retain its resources and program"); // Provide this text argument for the operation above.
445    const unsigned queries = observed.waits; // Set queries from this expression.
446    std::thread discard( // Declare discard with the parameters continued below.
447        [](shdr::submission dropped) { // Continue the expression using shdr, submission, dropped.
448            (void)dropped; // Continue the expression using void, dropped.
449        }, // Finish this declaration.
450        std::move(completion)); // Apply move to these arguments.
451    discard.join(); // Apply join to these arguments.
452    require(observed.finishes == 0 && observed.maps == 0 && observed.waits == queries, // Check that this operation satisfies the expected behavior.
453            "discarding a GPU submission called GL or waited on the worker thread"); // Provide this text argument for the operation above.
454    require(!input_lifetime.expired() && !output_lifetime.expired() && !program_lifetime.expired(), // Check that this operation satisfies the expected behavior.
455            "discarding a pending submission released resources before retirement"); // Provide this text argument for the operation above.
456    const std::chrono::steady_clock::time_point deadline = // Set deadline from this expression.
457        std::chrono::steady_clock::now() + std::chrono::seconds(10); // Apply now to these arguments.
458    while (!program_lifetime.expired() && std::chrono::steady_clock::now() < deadline) { // Poll deferred retirement until retained program ownership is released or the deadline arrives.
459        device.collect_garbage(); // Apply collect garbage to these arguments.
460        std::this_thread::yield(); // Apply yield to these arguments.
461    } // End this block.
462    require(input_lifetime.expired() && output_lifetime.expired() && program_lifetime.expired(), // Check that this operation satisfies the expected behavior.
463            "completed discarded submission retained resources indefinitely"); // Provide this text argument for the operation above.
464    require(observed.finishes == 0 && observed.maps == 0 && observed.blocking_waits == 0, // Check that this operation satisfies the expected behavior.
465            "deferred GPU retirement performed a blocking wait or readback"); // Provide this text argument for the operation above.
466} // End this block.
467
468void test_submission_context_lifetimes(shdr::device& original) { // Implement test submission context lifetimes for the operations below.
469    for (unsigned order = 0; order < 2; ++order) { // Iterate over order for this calculation.
470        std::unique_ptr<shdr::headless_device> device( // Declare device with the parameters continued below.
471            new shdr::headless_device(original.shader_profile())); // Declare shdr for the following calculation.
472        std::weak_ptr<shdr::detail::device_state> device_lifetime = device->state(); // Set device lifetime from this expression.
473        std::weak_ptr<shdr::detail::resource_state> resource_lifetime; // Declare resource lifetime for the following calculation.
474        std::weak_ptr<shdr::detail::program_state> program_lifetime; // Declare program lifetime for the following calculation.
475        shdr::submission completion; // Declare completion for the following calculation.
476        { // Begin this local scope.
477            shdr::buffer<float> input{3.0f, 7.0f}, output(2); // Declare input for the following calculation.
478            auto shader = shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
479            configure_affine(*shader, input, output, 2); // Apply configure affine to these arguments.
480            shdr::pipeline pipeline(shdr::dispatch(*shader, shdr::extent(1))); // Add a shader dispatch with the required workgroup counts.
481            completion = pipeline.submit(*device); // Submit work and retain its completion handle.
482            resource_lifetime = output.state(); // Set resource lifetime from this expression.
483            program_lifetime = shader->program_state(); // Set program lifetime from this expression.
484        } // End this block.
485        if (order == 0) { // Check this condition before entering the following branch.
486            completion = shdr::submission(); // Set completion from this expression.
487            device.reset(); // Apply reset to these arguments.
488        } else { // Destroy the device wrapper first while the live submission retains its context.
489            device.reset(); // Apply reset to these arguments.
490            require(!device_lifetime.expired(), "live submission lost its owned context"); // Check that this operation satisfies the expected behavior.
491            completion = shdr::submission(); // Set completion from this expression.
492        } // End this block.
493        require(resource_lifetime.expired() && program_lifetime.expired() && // Check that this operation satisfies the expected behavior.
494                    device_lifetime.expired(), // Apply expired to these arguments.
495                "device and submission destruction formed a resource ownership cycle"); // Provide this text argument for the operation above.
496    } // End this block.
497    original.collect_garbage(); // Apply collect garbage to these arguments.
498} // End this block.
499
500} // namespace
501
502int main(int argc, char** argv) { // Run this example and validate its results.
503    try { // Run the operation while retaining the error handling below.
504        shdr_examples::runner run(argc, argv); // Construct run with these initial values.
505        if (!run.device()) // Check this condition before entering the following branch.
506            throw std::invalid_argument("test_gpu requires --gpu PROFILE"); // Report test_gpu requires --gpu PROFILE.
507        test_external_buffers(*run.device()); // Apply test external buffers to these arguments.
508        test_shader_errors(*run.device()); // Apply test shader errors to these arguments.
509        test_typed_images(*run.device()); // Apply test typed images to these arguments.
510        test_headless_context_lifetimes(*run.device()); // Apply test headless context lifetimes to these arguments.
511        test_gpu_submissions(*run.device()); // Apply test gpu submissions to these arguments.
512        test_dropped_submissions(*run.device()); // Apply test dropped submissions to these arguments.
513        test_submission_context_lifetimes(*run.device()); // Apply test submission context lifetimes to these arguments.
514        std::cout << "GPU ownership, typed images and async completion tests passed\n"; // Write this part of the example result or diagnostic.
515        return 0; // Report successful completion.
516    } catch (const shdr_examples::context_unavailable& error) { // Report the exception from the preceding operation.
517        std::cerr << "SKIP: " << error.what() << "\n"; // Write this part of the example result or diagnostic.
518        return 77; // Report an unavailable optional GPU backend.
519    } catch (const std::exception& error) { // Report the exception from the preceding operation.
520        std::cerr << error.what() << "\n"; // Write this part of the example result or diagnostic.
521        return 1; // Report a failed check or operation.
522    } // End this block.
523} // End this block.