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.