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.