Source browser
tests/test_runtime.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 "shdr.h" // Include shdr.h declarations.
2#include <cmath> // Include cmath declarations.
3#include <iostream> // Include iostream declarations.
4#include <stdexcept> // Include stdexcept declarations.
5#include <string> // Include string declarations.
6
7// Write distinct 3D coordinates and validate every compute invocation builtin.
8SHDR_BEGIN(coordinates_kernel, 2, 3, 2) // Apply SHDR BEGIN to these arguments.
9SHDR_BUFFER(0, uint, coordinates, writeonly) // Apply SHDR BUFFER to these arguments.
10SHDR_BUFFER(1, uint, valid, writeonly) // Apply SHDR BUFFER to these arguments.
11SHDR_MAIN() { // Apply SHDR MAIN to these arguments.
12 uint width = gl_NumWorkGroups.x * gl_WorkGroupSize.x; // Set width from this expression.
13 uint height = gl_NumWorkGroups.y * gl_WorkGroupSize.y; // Set height from this expression.
14 uint index = gl_GlobalInvocationID.x + // Set index from this expression.
15 width * (gl_GlobalInvocationID.y + height * gl_GlobalInvocationID.z); // Continue the expression using width, gl Global Invocation ID, y.
16 coordinates[index] = // Set coordinates[index] from this expression.
17 gl_GlobalInvocationID.x + 100u * gl_GlobalInvocationID.y + 10000u * gl_GlobalInvocationID.z; // Continue the expression using gl Global Invocation ID, x, gl Global Invocation ID.
18 uint local_index = // Set local index from this expression.
19 gl_LocalInvocationID.x + // Continue the expression using gl Local Invocation ID, x.
20 gl_WorkGroupSize.x * (gl_LocalInvocationID.y + gl_WorkGroupSize.y * gl_LocalInvocationID.z); // Continue the expression using gl Work Group Size, x, gl Local Invocation ID.
21 valid[index] = uint( // Set valid[index] from this expression.
22 gl_LocalInvocationIndex == local_index && // Continue the expression using gl Local Invocation Index, local index.
23 gl_GlobalInvocationID.x == gl_WorkGroupID.x * gl_WorkGroupSize.x + gl_LocalInvocationID.x && // Continue the expression using gl Global Invocation ID, x, gl Work Group ID.
24 gl_GlobalInvocationID.y == gl_WorkGroupID.y * gl_WorkGroupSize.y + gl_LocalInvocationID.y && // Continue the expression using gl Global Invocation ID, y, gl Work Group ID.
25 gl_GlobalInvocationID.z == gl_WorkGroupID.z * gl_WorkGroupSize.z + gl_LocalInvocationID.z); // Continue the expression using gl Global Invocation ID, z, gl Work Group ID.
26} // End this block.
27SHDR_END() // Apply SHDR END to these arguments.
28
29namespace { // Group these helpers within their namespace.
30
31void require(bool condition, const std::string& message) { // Check that this operation satisfies the expected behavior.
32 if (!condition) // Check this condition before entering the following branch.
33 throw std::runtime_error(message); // Report this failed operation.
34} // End this block.
35
36void near(float actual, float expected, const std::string& message) { // Implement near for the operations below.
37 require(std::isfinite(actual) && std::fabs(actual - expected) < 0.0001f, message); // Check that this operation satisfies the expected behavior.
38} // End this block.
39
40template <class Function> void throws(Function function, const std::string& message) { // Accept the value types or dimensions used by this template.
41 bool caught = false; // Set caught from this expression.
42 try { // Run the operation while retaining the error handling below.
43 function(); // Apply function to these arguments.
44 } catch (const std::exception&) { // Report the exception from the preceding operation.
45 caught = true; // Set caught from this expression.
46 } // End this block.
47 require(caught, message); // Check that this operation satisfies the expected behavior.
48} // End this block.
49
50void test_borrowed_rebinding_and_repeat() { // Implement test borrowed rebinding and repeat for the operations below.
51 float first_values[] = {1, 2, 3, 4, 5}; // Declare first values for the following calculation.
52 float second_values[] = {10, 20, 30, 40, 50}; // Declare second values for the following calculation.
53 float output_values[5] = {}; // Set output values[5] from this expression.
54 shdr::buffer<float> first(first_values, 5), second(second_values, 5), output(output_values, 5); // Construct first with these initial values.
55 auto affine_owner = shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
56 shdr::kernel& affine = *affine_owner; // Set affine from this expression.
57 affine.bind("input_values", first); // Connect this shader port to its application resource.
58 affine.bind("output_values", output); // Connect this shader port to its application resource.
59 affine.set<unsigned>("count", 5u); // Supply this shader uniform with its typed application value.
60 affine.set<float>("scale", 2.0f); // Supply this shader uniform with its typed application value.
61 affine.set<float>("offset", 3.0f); // Supply this shader uniform with its typed application value.
62 shdr::pipeline pipeline; // Declare pipeline for the following calculation.
63 pipeline += shdr::dispatch(affine, shdr::extent(1)); // Add a shader dispatch with the required workgroup counts.
64 pipeline.run(); // Execute the configured pipeline on the requested backend.
65 for (unsigned i = 0; i < 5; ++i) // Iterate over i for this calculation.
66 near(output_values[i], first_values[i] * 2 + 3, "borrowed host output"); // Apply near to these arguments.
67
68 // Ports and uniforms remain dynamically bindable after pipeline construction.
69 affine.bind("input_values", second); // Connect this shader port to its application resource.
70 affine.set<float>("scale", 0.5f); // Supply this shader uniform with its typed application value.
71 affine.set<float>("offset", -1.0f); // Supply this shader uniform with its typed application value.
72 pipeline.run(); // Execute the configured pipeline on the requested backend.
73 for (unsigned i = 0; i < 5; ++i) // Iterate over i for this calculation.
74 near(output_values[i], second_values[i] * 0.5f - 1, "rebound host output"); // Apply near to these arguments.
75
76 affine.bind("input_values", // Connect this shader port to its application resource.
77 output); // Per-index input/output aliasing is legal for this shader.
78 affine.set<float>("scale", 1.0f); // Supply this shader uniform with its typed application value.
79 affine.set<float>("offset", 2.0f); // Supply this shader uniform with its typed application value.
80 pipeline.run(); // Execute the configured pipeline on the requested backend.
81 for (unsigned i = 0; i < 5; ++i) // Iterate over i for this calculation.
82 near(output_values[i], second_values[i] * 0.5f + 1, "aliased host output"); // Apply near to these arguments.
83} // End this block.
84
85void test_complete_pipeline_composition() { // Verify algorithm-block composition, order and self-append ownership.
86 shdr::buffer<float> values{2.0f}; // Keep one shared in-place value so dispatch order is observable.
87 auto first = std::shared_ptr<shdr::kernel>(shdr::make_kernel("affine_kernel").release()); // Retain the first shader through shared dispatch ownership.
88 auto second = std::shared_ptr<shdr::kernel>(shdr::make_kernel("affine_kernel").release()); // Retain the second independently configured shader.
89 first->bind("input_values", values).bind("output_values", values).set("count", 1u).set("scale", 2.0f).set("offset", 1.0f); // Define x becoming 2x plus one.
90 second->bind("input_values", values).bind("output_values", values).set("count", 1u).set("scale", 3.0f).set("offset", -2.0f); // Define x becoming 3x minus two.
91 const shdr::stage a = shdr::dispatch(first, shdr::extent(1)); // Keep ownership in the first dispatch.
92 const shdr::stage b = shdr::dispatch(second, shdr::extent(1), shdr::backend::cpu); // Keep the explicit CPU choice in the second dispatch.
93 const shdr::pipeline first_block(a), second_block(b); // Build independently reusable processing blocks.
94 const shdr::pipeline joined = first_block >> second_block; // Join complete blocks with the public composition operator.
95 require(joined.size() == 2u && joined.stages()[1].target == shdr::backend::cpu, "joined block order and backend choice"); // Verify retained order and backend metadata.
96 joined.run(); // Apply 2 to 5 and then 5 to 13 in the linked resources.
97 near(static_cast<const shdr::buffer<float>&>(values).data()[0], 13.0f, "joined block execution order"); // Make reversed dispatch order fail this check.
98 values.data()[0] = 2.0f; // Restore the original resource for prepend validation.
99 (a >> second_block).run(); // Compose an individual dispatch before a complete processing block.
100 near(static_cast<const shdr::buffer<float>&>(values).data()[0], 13.0f, "stage before complete block"); // Verify the mixed composition overload.
101 shdr::pipeline repeated = first_block; // Start with one retained affine dispatch.
102 repeated += repeated; // Append safely from the same vector even if capacity grows.
103 require(repeated.size() == 2u && first_block.size() == 1u, "self append preserves source blocks"); // Verify a finite self-append copy and the separate source block.
104 first.reset(); // Release the caller's first shader reference before execution.
105 values.data()[0] = 2.0f; // Restore the input before the repeated retained dispatches.
106 repeated.snapshot().run(); // Check that deferred snapshot composition retains shader ownership.
107 near(static_cast<const shdr::buffer<float>&>(values).data()[0], 11.0f, "self appended retained stages"); // Verify two first-stage executions in sequence.
108 shdr::pipeline appended = first_block; // Exercise ordinary complete-block append separately.
109 appended += second_block; // Append the second block through the public overload.
110 appended += shdr::pipeline(); // Treat an empty processing block as an ordinary no-op.
111 values.data()[0] = 2.0f; // Restore the same original input for append validation.
112 appended.run(); // Execute both retained linked stages after the caller released one owner.
113 near(static_cast<const shdr::buffer<float>&>(values).data()[0], 13.0f, "complete block append"); // Verify dispatch ownership and order together.
114} // All three composition forms preserve resource sharing and dispatch lifetimes.
115
116void test_three_dimensional_dispatch() { // Implement test three dimensional dispatch for the operations below.
117 shdr::buffer<unsigned> coordinates(4 * 6 * 4), valid(4 * 6 * 4); // Construct coordinates with these initial values.
118 coordinates_kernel shader; // Declare shader for the following calculation.
119 shader.coordinates = coordinates; // Set shader.coordinates from this expression.
120 shader.valid = valid; // Set shader.valid from this expression.
121 shdr::pipeline pipeline; // Declare pipeline for the following calculation.
122 pipeline += shdr::dispatch(shader, shdr::extent(2, 2, 2)); // Add a shader dispatch with the required workgroup counts.
123 pipeline.run(); // Execute the configured pipeline on the requested backend.
124 const shdr::buffer<unsigned>& read_coordinates = coordinates; // Use a constant buffer view so GPU writes synchronize before host reads.
125 const shdr::buffer<unsigned>& read_valid = valid; // Use a constant buffer view so GPU writes synchronize before host reads.
126 for (unsigned z = 0; z < 4; ++z) // Iterate over z for this calculation.
127 for (unsigned y = 0; y < 6; ++y) // Iterate over y for this calculation.
128 for (unsigned x = 0; x < 4; ++x) { // Iterate over x for this calculation.
129 const unsigned index = x + 4 * (y + 6 * z); // Set index from this expression.
130 require(read_coordinates.data()[index] == x + 100 * y + 10000 * z, // Check that this operation satisfies the expected behavior.
131 "3D global invocation ID"); // Provide this text argument for the operation above.
132 require(read_valid.data()[index] == 1, "3D local and workgroup builtins"); // Check that this operation satisfies the expected behavior.
133 } // End this block.
134} // End this block.
135
136void test_dynamic_binding_errors() { // Implement test dynamic binding errors for the operations below.
137 auto shader_owner = shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
138 shdr::kernel& shader = *shader_owner; // Set shader from this expression.
139 shdr::pipeline pipeline; // Declare pipeline for the following calculation.
140 pipeline += shdr::dispatch(shader, shdr::extent(1)); // Add a shader dispatch with the required workgroup counts.
141 throws( // Report this failed operation.
142 [&] { // Complete this expression or declaration.
143 pipeline.run(); // Execute the configured pipeline on the requested backend.
144 }, // Finish this declaration.
145 "unbound resources must report an error"); // Provide this text argument for the operation above.
146 shdr::buffer<float> values(4); // Construct values with these initial values.
147 shdr::kernel& generic = shader; // Set generic from this expression.
148 throws( // Report this failed operation.
149 [&] { // Complete this expression or declaration.
150 generic.bind("misspelled", values); // Connect this shader port to its application resource.
151 }, // Finish this declaration.
152 "unknown buffer port must report an error"); // Provide this text argument for the operation above.
153 throws( // Report this failed operation.
154 [&] { // Complete this expression or declaration.
155 generic.set<float>("misspelled", 1.0f); // Supply this shader uniform with its typed application value.
156 }, // Finish this declaration.
157 "unknown uniform must report an error"); // Provide this text argument for the operation above.
158 throws( // Report this failed operation.
159 [&] { // Complete this expression or declaration.
160 generic.set<float>("count", 1.0f); // Supply this shader uniform with its typed application value.
161 }, // Finish this declaration.
162 "uniform type mismatch must report an error"); // Provide this text argument for the operation above.
163
164 generic.bind("input_values", values); // Connect this shader port to its application resource.
165 generic.bind("output_values", values); // Connect this shader port to its application resource.
166 generic.set<unsigned>("count", 4u); // Supply this shader uniform with its typed application value.
167 generic.set<float>("scale", 1.0f); // Supply this shader uniform with its typed application value.
168 generic.set<float>("offset", 2.0f); // Supply this shader uniform with its typed application value.
169 pipeline.run(); // Execute the configured pipeline on the requested backend.
170 const shdr::buffer<float>& read_values = values; // Use a constant buffer view so GPU writes synchronize before host reads.
171 for (unsigned i = 0; i < 4; ++i) // Iterate over i for this calculation.
172 near(read_values.data()[i], 2, "name-based resource and uniform binding"); // Apply near to these arguments.
173
174 generic.set<unsigned>("count", 5u); // Supply this shader uniform with its typed application value.
175 throws( // Report this failed operation.
176 [&] { // Complete this expression or declaration.
177 pipeline.run(); // Execute the configured pipeline on the requested backend.
178 }, // Finish this declaration.
179 "CPU shader out-of-range SSBO access must report an error"); // Provide this text argument for the operation above.
180 generic.set<unsigned>("count", 4u); // Supply this shader uniform with its typed application value.
181 shdr::pipeline gpu_only; // Declare gpu only for the following calculation.
182 gpu_only += shdr::dispatch(shader, shdr::extent(1), shdr::backend::gpu); // Add a shader dispatch with the required workgroup counts.
183 throws( // Report this failed operation.
184 [&] { // Complete this expression or declaration.
185 gpu_only.run(); // Execute the configured pipeline on the requested backend.
186 }, // Finish this declaration.
187 "explicit GPU stage requires a GPU device"); // Provide this text argument for the operation above.
188} // End this block.
189
190void test_linked_buffer_image_pipeline() { // Implement test linked buffer image pipeline for the operations below.
191 const unsigned width = 7, height = 5; // Set width from this expression.
192 shdr::buffer<float> input(width * height), output(width * height); // Construct input with these initial values.
193 shdr::image2d<float, 1> original(width, height), filtered(width, height); // Construct original with these initial values.
194 for (unsigned i = 0; i < input.size(); ++i) // Iterate over i for this calculation.
195 input.data()[i] = float(i); // Apply data to these arguments.
196 auto into_image_owner = shdr::make_kernel("buffer_to_image_kernel"); // Create the linked buffer to image kernel instance.
197 shdr::kernel& into_image = *into_image_owner; // Set into image from this expression.
198 into_image.bind("input_values", input); // Connect this shader port to its application resource.
199 into_image.bind("output_image", original); // Connect this shader port to its application resource.
200 auto blur_owner = shdr::make_kernel("blur_kernel"); // Create the linked blur kernel instance.
201 shdr::kernel& blur = *blur_owner; // Set blur from this expression.
202 blur.bind("input_image", original); // Connect this shader port to its application resource.
203 blur.bind("output_image", filtered); // Connect this shader port to its application resource.
204 auto into_buffer_owner = shdr::make_kernel("image_to_buffer_kernel"); // Create the linked image to buffer kernel instance.
205 shdr::kernel& into_buffer = *into_buffer_owner; // Set into buffer from this expression.
206 into_buffer.bind("input_image", filtered); // Connect this shader port to its application resource.
207 into_buffer.bind("output_values", output); // Connect this shader port to its application resource.
208 shdr::pipeline pipeline = shdr::dispatch(into_image, shdr::extent(1, 1)) >> // Add a shader dispatch with the required workgroup counts.
209 shdr::dispatch(blur, shdr::extent(1, 1)) >> // Add a shader dispatch with the required workgroup counts.
210 shdr::dispatch(into_buffer, shdr::extent(1, 1)); // Add a shader dispatch with the required workgroup counts.
211 pipeline.run(); // Execute the configured pipeline on the requested backend.
212 const shdr::buffer<float>& read_output = output; // Use a constant buffer view so GPU writes synchronize before host reads.
213 near(read_output.data()[0], 8.0f / 3.0f, "clamped blur upper corner"); // Apply near to these arguments.
214 near(read_output.data()[17], 17.0f, "clamped blur center"); // Apply near to these arguments.
215 near(read_output.data()[34], 34.0f - 8.0f / 3.0f, "clamped blur lower corner"); // Apply near to these arguments.
216} // End this block.
217
218} // namespace
219
220int main() { // Run this example and validate its results.
221 try { // Run the operation while retaining the error handling below.
222 test_borrowed_rebinding_and_repeat(); // Apply test borrowed rebinding and repeat to these arguments.
223 test_complete_pipeline_composition(); // Validate complete algorithm-block composition and retained ownership.
224 test_three_dimensional_dispatch(); // Apply test three dimensional dispatch to these arguments.
225 test_dynamic_binding_errors(); // Apply test dynamic binding errors to these arguments.
226 test_linked_buffer_image_pipeline(); // Apply test linked buffer image pipeline to these arguments.
227 std::cout << "CPU runtime tests passed\n"; // Write this part of the example result or diagnostic.
228 return 0; // Report successful completion.
229 } catch (const std::exception& error) { // Report the exception from the preceding operation.
230 std::cerr << error.what() << "\n"; // Write this part of the example result or diagnostic.
231 return 1; // Report a failed check or operation.
232 } // End this block.
233} // End this block.