Source browser

examples/dynamic.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#include <dlfcn.h> // Include dlfcn.h declarations.
3
4namespace { // Group these helpers within their namespace.
5
6/// Own a shader plugin until its kernel, stages and GPU program have been destroyed.
7class plugin { // Declare plugin.
8    void* handle_; // Declare handle for the following calculation.
9    typedef shdr::kernel* (*create_fn)(); // Describe the exported factory's stable function signature.
10    typedef void (*destroy_fn)(shdr::kernel*); // Describe the exported matching destructor signature.
11    destroy_fn destroy_; // Destroy the ABI object using the same module that allocated it.
12    shdr::kernel* kernel_; // Keep the loaded module's dual-compiled shader instance.
13
14    template <class Function> Function symbol(const char* name) { // Accept the value types or dimensions used by this template.
15        dlerror(); // Clear any earlier loader error before looking up this symbol.
16        void* address = dlsym(handle_, name); // Look up the requested exported function in the loaded module.
17        const char* error = dlerror(); // Check whether this symbol lookup produced a loader error.
18        if (error) // Check this condition before entering the following branch.
19            throw std::runtime_error(error); // Report this failed operation.
20        return reinterpret_cast<Function>(address); // Use the POSIX loader result with its documented exported function type.
21    } // End this block.
22
23  public: // Set public access for the following class members.
24    explicit plugin(const char* path) : handle_(0), destroy_(0), kernel_(0) { // Implement plugin for the operations below.
25        handle_ = dlopen(path, RTLD_NOW | RTLD_LOCAL); // Load the module and resolve its dependencies before creating its shader.
26        if (!handle_) // Check this condition before entering the following branch.
27            throw std::runtime_error(dlerror()); // Report this failed operation.
28        try { // Run the operation while retaining the error handling below.
29            create_fn create = symbol<create_fn>("shdr_create_kernel"); // Resolve the plugin factory through its exported C entry point.
30            destroy_ = symbol<destroy_fn>("shdr_destroy_kernel"); // Find the matching module destructor before taking ownership.
31            kernel_ = create(); // Create the module's compiled CPU kernel and embedded GPU variant.
32            if (!kernel_) // Check this condition before entering the following branch.
33                throw std::runtime_error("plugin returned a null kernel"); // Report plugin returned a null kernel.
34        } catch (...) { // Report the exception from the preceding operation.
35            if (kernel_ && destroy_) // Check this condition before entering the following branch.
36                destroy_(kernel_); // Release the module-owned kernel through its exported destructor.
37            dlclose(handle_); // Release the module only after its kernel has been destroyed.
38            throw; // Report this failed operation.
39        } // End this block.
40    } // End this block.
41
42    ~plugin() { // Destroy the shader before unloading the code implementing its virtual methods.
43        if (kernel_) // Check this condition before entering the following branch.
44            destroy_(kernel_); // Release the kernel while its module code is still loaded.
45        if (handle_) // Check this condition before entering the following branch.
46            dlclose(handle_); // Release the module only after its kernel has been destroyed.
47    } // End this block.
48    shdr::kernel& kernel() { // Implement kernel for the operations below.
49        return *kernel_; // Expose the same kernel interface used by statically linked stages.
50    } // End this block.
51    plugin(const plugin&) = delete; // Keep one owner for the loaded module and its kernel.
52    plugin& operator=(const plugin&) = delete; // Prevent two wrappers from unloading the same module.
53}; // Finish this declaration.
54
55} // namespace
56
57/// Load a CPU shader dynamically and link its corresponding GPU shader identically.
58int main(int argc, char** argv) { // Run this example and validate its results.
59    try { // Run the operation while retaining the error handling below.
60        shdr_examples::runner run(argc, argv); // Select CPU execution or the requested headless GPU profile.
61        plugin library(SHDR_PLUGIN_PATH); // Keep the plugin loaded longer than every pipeline stage referring to its kernel.
62        const unsigned count = 193; // Use a nonmultiple of the workgroup size to exercise the shader bounds guards.
63        shdr::buffer<float> input(count), scaled(count), clipped(count), result(1); // Allocate distinct resources for the input, intermediate stages and final result.
64        shdr_examples::randomize(input, 1104u, 0.0f, 24.0f); // Fill the input with reproducible random values.
65
66        // Generic name-based binding supports a kernel whose C++ class is unknown here.
67        shdr::kernel& affine = library.kernel(); // Use the loaded module's dual-compiled kernel through the common base interface.
68        affine.bind("input_values", input); // Read the resource produced by the preceding stage or the original host input.
69        affine.bind("output_values", scaled); // Write the shared resource consumed by the next stage or the final host check.
70        affine.set<unsigned>("count", count); // Limit the shader to the actual number of valid buffer elements.
71        affine.set<float>("scale", 0.5f); // Configure the first stage's multiplicative gain.
72        affine.set<float>("offset", -2.0f); // Configure the bias added after the first stage scales each input.
73
74        auto clip_owner = shdr::make_kernel("clamp_kernel"); // Create the linked clamp kernel instance.
75        shdr::kernel& clip = *clip_owner; // Retain the owned shader instance for every stage that references it.
76        clip.bind("input_values", scaled); // Read the resource produced by the preceding stage or the original host input.
77        clip.bind("output_values", clipped); // Write the shared resource consumed by the next stage or the final host check.
78        clip.set<unsigned>("count", count); // Limit the shader to the actual number of valid buffer elements.
79        clip.set<float>("lower", 0.0f); // Set the lower clipping bound in the second stage.
80        clip.set<float>("upper", 5.0f); // Set the upper clipping bound in the second stage.
81
82        auto sum_owner = shdr::make_kernel("sum_kernel"); // Create the linked sum kernel instance.
83        shdr::kernel& sum = *sum_owner; // Retain the owned shader instance for every stage that references it.
84        sum.bind("input_values", clipped); // Read the resource produced by the preceding stage or the original host input.
85        sum.bind("output_values", result); // Write the shared resource consumed by the next stage or the final host check.
86        sum.set<unsigned>("count", count); // Limit the shader to the actual number of valid buffer elements.
87
88        shdr::pipeline pipeline = shdr::dispatch(affine, shdr::extent((count + 63) / 64)) >> // Start the pipeline with the affine shader and enough 64-wide groups.
89                                  shdr::dispatch(clip, shdr::extent((count + 31) / 32)) >> // Link the clipping shader using its own 32-wide group size.
90                                  shdr::dispatch(sum, shdr::extent(1)); // Return to the selected backend for the final one-invocation reduction.
91        run.run(pipeline); // Execute the configured pipeline on the requested backend.
92
93        float expected = 0.0f; // Start the independent scalar reference at zero.
94        const shdr::buffer<float>& read_input = input; // Use a constant buffer view so GPU writes synchronize before host reads.
95        for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
96            expected += std::min(5.0f, std::max(0.0f, read_input.data()[i] * 0.5f - 2.0f)); // Apply the same affine and clipping formula directly to the original input.
97        const shdr::buffer<float>& read_result = result; // Use a constant buffer view so GPU writes synchronize before host reads.
98        shdr_examples::require_close(read_result.data()[0], expected, "dynamic shader sum"); // Check the computed value against the independent reference.
99        std::cout << (run.gpu() ? "GPU" : "CPU") // Write this part of the example result or diagnostic.
100                  << " dynamic shader pipeline: " << read_result.data()[0] << "\n"; // Write this part of the example result or diagnostic.
101        return 0; // Report successful completion.
102#ifdef SHDR_HAVE_EGL // Enable this branch when its feature macro is defined.
103    } catch (const shdr_examples::context_unavailable& error) { // Report the exception from the preceding operation.
104        std::cerr << "SKIP: " << error.what() << "\n"; // Write this part of the example result or diagnostic.
105        return 77; // Report an unavailable optional GPU backend.
106#endif // Finish this conditional compilation branch.
107    } catch (const std::exception& error) { // Report the exception from the preceding operation.
108        std::cerr << error.what() << "\n"; // Write this part of the example result or diagnostic.
109        return 1; // Report a failed check or operation.
110    } // End this block.
111} // End this block.