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.