Source browser
examples/async.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/** @file async.cpp
2 * Three resource slots keep CPU workers or GPU dispatches in flight while an
3 * application polls completions. Binding and uniform snapshots let one producer
4 * kernel be reused. Resource slots are recycled after their jobs complete.
5 */
6#include "runner.hpp" // Include runner.hpp declarations.
7#include <chrono> // Include chrono declarations.
8#include <future> // Include future declarations.
9#include <thread> // Include thread declarations.
10#include <vector> // Include vector declarations.
11
12namespace { // Group these helpers within their namespace.
13const unsigned element_count = 257; // Set element count from this expression.
14const unsigned frame_count = 32; // Set frame count from this expression.
15const unsigned slot_count = 3; // Set slot count from this expression.
16struct slot { // Declare slot.
17 shdr::buffer<float> input, output; // Declare input for the following calculation.
18 shdr::submission gpu_done; // Declare gpu done for the following calculation.
19 std::future<void> cpu_done; // Declare cpu done for the following calculation.
20 bool occupied; // Declare occupied for the following calculation.
21 unsigned frame; // Declare frame for the following calculation.
22 slot() : input(element_count), output(element_count), occupied(false), frame(0) { // Apply slot to these arguments.
23 } // End this block.
24}; // Finish this declaration.
25
26void check(const slot& resource, unsigned index) { // Implement check for the operations below.
27 const shdr::buffer<float>& result = resource.output; // Use a constant buffer view so GPU writes synchronize before host reads.
28 const float scale = float(resource.frame + 1); // Set scale from this expression.
29 const float offset = -float(resource.frame); // Set offset from this expression.
30 for (unsigned i = 0; i < element_count; ++i) // Iterate over i for this calculation.
31 shdr_examples::require_close( // Check the computed value against the independent reference.
32 result.data()[i], (float(i) + float(index * 10)) * scale + offset, "async ring result"); // Apply data to these arguments.
33} // End this block.
34
35void run_ring(shdr_examples::runner& runner) { // Implement run ring for the operations below.
36 std::vector<slot> slots(slot_count); // Construct slots with these initial values.
37 for (unsigned index = 0; index < slot_count; ++index) // Iterate over index for this calculation.
38 for (unsigned i = 0; i < element_count; ++i) // Iterate over i for this calculation.
39 slots[index].input.data()[i] = float(i + index * 10); // Apply data to these arguments.
40 auto shader = shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
41 shader->set<unsigned>("count", element_count); // Supply this shader uniform with its typed application value.
42 shdr::pipeline pipeline(shdr::dispatch(*shader, shdr::extent((element_count + 63) / 64))); // Add a shader dispatch with the required workgroup counts.
43 std::unique_ptr<shdr::executor> workers; // Declare workers for the following calculation.
44 if (!runner.gpu()) // Check this condition before entering the following branch.
45 workers.reset(new shdr::executor(2, slot_count)); // Apply reset to these arguments.
46 if (runner.gpu()) { // Check this condition before entering the following branch.
47 // Initial program compilation and every slot's allocation/upload happen
48 // before entering the polling loop.
49 for (unsigned index = 0; index < slot_count; ++index) { // Iterate over index for this calculation.
50 shader->bind("input_values", slots[index].input) // Connect this shader port to its application resource.
51 .bind("output_values", slots[index].output); // Connect this shader port to its application resource.
52 pipeline.prepare(*runner.device()); // Apply prepare to these arguments.
53 } // End this block.
54 } // End this block.
55 unsigned produced = 0, completed = 0; // Set produced from this expression.
56 while (completed < frame_count) { // Pump the resource ring until every submitted frame completes.
57 bool progress = false; // Set progress from this expression.
58 for (unsigned index = 0; index < slot_count; ++index) { // Iterate over index for this calculation.
59 slot& resource = slots[index]; // Set resource from this expression.
60 if (resource.occupied) { // Check this condition before entering the following branch.
61 const bool ready = runner.gpu() // Set ready from this expression.
62 ? resource.gpu_done.ready() // Apply ready to these arguments.
63 : resource.cpu_done.wait_for(std::chrono::nanoseconds(0)) == // Declare resource for the following calculation.
64 std::future_status::ready; // Continue the expression using std, future status, ready.
65 if (!ready) // Check this condition before entering the following branch.
66 continue; // Skip the remaining work for this loop iteration.
67 if (!runner.gpu()) // Check this condition before entering the following branch.
68 resource.cpu_done.get(); // Apply get to these arguments.
69 check(resource, index); // Apply check to these arguments.
70 resource.occupied = false; // Set resource.occupied from this expression.
71 ++completed; // Continue the expression using completed.
72 progress = true; // Set progress from this expression.
73 } // End this block.
74 if (produced < frame_count) { // Check this condition before entering the following branch.
75 resource.frame = produced++; // Set resource.frame from this expression.
76 shader->bind("input_values", resource.input).bind("output_values", resource.output); // Connect this shader port to its application resource.
77 shader->set<float>("scale", float(resource.frame + 1)) // Supply this shader uniform with its typed application value.
78 .set<float>("offset", -float(resource.frame)); // Supply this shader uniform with its typed application value.
79 if (runner.gpu()) // Check this condition before entering the following branch.
80 resource.gpu_done = pipeline.submit(*runner.device()); // Submit work and retain its completion handle.
81 else // Queue the frame snapshot on the CPU worker pool.
82 resource.cpu_done = workers->submit(pipeline); // Submit work and retain its completion handle.
83 resource.occupied = true; // Set resource.occupied from this expression.
84 progress = true; // Set progress from this expression.
85 } // End this block.
86 } // End this block.
87 if (runner.gpu()) // Check this condition before entering the following branch.
88 runner.device()->collect_garbage(); // Apply device to these arguments.
89 // An event loop can perform other application work between pump passes.
90 if (!progress) // Check this condition before entering the following branch.
91 std::this_thread::yield(); // Apply yield to these arguments.
92 } // End this block.
93 if (workers) // Check this condition before entering the following branch.
94 workers->shutdown(); // Apply shutdown to these arguments.
95 if (runner.gpu()) { // Check this condition before entering the following branch.
96 for (unsigned index = 0; index < slot_count; ++index) // Iterate over index for this calculation.
97 if (slots[index].gpu_done.valid()) // Check this condition before entering the following branch.
98 slots[index].gpu_done.wait(std::chrono::nanoseconds(0)); // Apply wait to these arguments.
99 runner.device()->collect_garbage(); // Apply device to these arguments.
100 } // End this block.
101 std::cout << (runner.gpu() ? "GPU" : "CPU") << " async ring: " << completed // Write this part of the example result or diagnostic.
102 << " frames verified\n"; // Write this part of the example result or diagnostic.
103} // End this block.
104} // namespace
105
106int main(int argc, char** argv) { // Run this example and validate its results.
107 try { // Run the operation while retaining the error handling below.
108 shdr_examples::runner runner(argc, argv); // Construct runner with these initial values.
109 run_ring(runner); // Apply run ring to these arguments.
110 return 0; // Report successful completion.
111#ifdef SHDR_HAVE_EGL // Enable this branch when its feature macro is defined.
112 } catch (const shdr_examples::context_unavailable& e) { // Report the exception from the preceding operation.
113 std::cerr << "SKIP: " << e.what() << '\n'; // Write this part of the example result or diagnostic.
114 return 77; // Report an unavailable optional GPU backend.
115#endif // Finish this conditional compilation branch.
116 } catch (const std::exception& e) { // Report the exception from the preceding operation.
117 std::cerr << e.what() << '\n'; // Write this part of the example result or diagnostic.
118 return 1; // Report a failed check or operation.
119 } // End this block.
120} // End this block.