Source browser

examples/hybrid.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 "runner.hpp" // Include runner.hpp declarations.
2
3/// Interleave GPU and CPU stages, shared buffers synchronize at backend changes.
4int main(int argc, char** argv) { // Run this example and validate its results.
5    try { // Run the operation while retaining the error handling below.
6        shdr_examples::runner run(argc, argv); // Select CPU execution or the requested headless GPU profile.
7        const unsigned count = 129; // Use a nonmultiple of the workgroup size to exercise the shader bounds guards.
8        shdr::buffer<float> input(count), scaled(count), clipped(count), result(1); // Allocate distinct resources for the input, intermediate stages and final result.
9        shdr_examples::randomize(input, 1103u, 0.0f, 4.0f); // Fill the input with reproducible random values.
10        auto affine_owner = shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
11        shdr::kernel& affine = *affine_owner; // Retain the owned shader instance for every stage that references it.
12        affine.bind("input_values", input); // Read the resource produced by the preceding stage or the original host input.
13        affine.bind("output_values", scaled); // Write the shared resource consumed by the next stage or the final host check.
14        affine.set<unsigned>("count", count); // Limit the shader to the actual number of valid buffer elements.
15        affine.set<float>("scale", 4.0f); // Configure the first stage's multiplicative gain.
16        affine.set<float>("offset", -1.0f); // Configure the bias added after the first stage scales each input.
17
18        auto clip_owner = shdr::make_kernel("clamp_kernel"); // Create the linked clamp kernel instance.
19        shdr::kernel& clip = *clip_owner; // Retain the owned shader instance for every stage that references it.
20        clip.bind("input_values", scaled); // Read the resource produced by the preceding stage or the original host input.
21        clip.bind("output_values", clipped); // Write the shared resource consumed by the next stage or the final host check.
22        clip.set<unsigned>("count", count); // Limit the shader to the actual number of valid buffer elements.
23        clip.set<float>("lower", 0.0f); // Set the lower clipping bound in the second stage.
24        clip.set<float>("upper", 7.0f); // Set the upper clipping bound in the second stage.
25
26        auto sum_owner = shdr::make_kernel("sum_kernel"); // Create the linked sum kernel instance.
27        shdr::kernel& sum = *sum_owner; // Retain the owned shader instance for every stage that references it.
28        sum.bind("input_values", clipped); // Read the resource produced by the preceding stage or the original host input.
29        sum.bind("output_values", result); // Write the shared resource consumed by the next stage or the final host check.
30        sum.set<unsigned>("count", count); // Limit the shader to the actual number of valid buffer elements.
31
32        shdr::pipeline pipeline = // Set pipeline from this expression.
33            shdr::dispatch(affine, shdr::extent((count + 63) / 64)) >> // Start the pipeline with the affine shader and enough 64-wide groups.
34            shdr::dispatch(clip, shdr::extent((count + 31) / 32), shdr::backend::cpu) >> // Force clipping onto the CPU so the shared buffer crosses both backend boundaries.
35            shdr::dispatch(sum, shdr::extent(1)); // Return to the selected backend for the final one-invocation reduction.
36        run.run(pipeline); // Execute the configured pipeline on the requested backend.
37        float expected = 0.0f; // Start the independent scalar reference at zero.
38        const shdr::buffer<float>& read_input = input; // Use a constant buffer view so GPU writes synchronize before host reads.
39        for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
40            expected += std::min(7.0f, std::max(0.0f, read_input.data()[i] * 4.0f - 1.0f)); // Apply the same affine and clipping formula directly to the original input.
41        const shdr::buffer<float>& read_result = result; // Use a constant buffer view so GPU writes synchronize before host reads.
42        shdr_examples::require_close(read_result.data()[0], expected, "hybrid pipeline sum"); // Check the computed value against the independent reference.
43        std::cout << (run.gpu() ? "GPU/CPU/GPU" : "CPU") << " pipeline: " << read_result.data()[0] // Write this part of the example result or diagnostic.
44                  << "\n"; // Write this part of the example result or diagnostic.
45        return 0; // Report successful completion.
46#ifdef SHDR_HAVE_EGL // Enable this branch when its feature macro is defined.
47    } catch (const shdr_examples::context_unavailable& error) { // Report the exception from the preceding operation.
48        std::cerr << "SKIP: " << error.what() << "\n"; // Write this part of the example result or diagnostic.
49        return 77; // Report an unavailable optional GPU backend.
50#endif // Finish this conditional compilation branch.
51    } catch (const std::exception& error) { // Report the exception from the preceding operation.
52        std::cerr << error.what() << "\n"; // Write this part of the example result or diagnostic.
53        return 1; // Report a failed check or operation.
54    } // End this block.
55} // End this block.