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.