Source browser

examples/benchmark.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 benchmark.cpp
2 * Seeded CPU/GPU comparisons for the example compute pipelines. Warmup excludes
3 * compilation and allocation. Steady-state timings include hybrid backend transfers.
4 * Final host readback and correctness comparisons occur after timed execution.
5 */
6#include "runner.hpp" // Include runner.hpp declarations.
7#include "evt3_support.hpp" // Include evt3_support.hpp declarations.
8#include "evt3_flow_checks.hpp" // Include evt3_flow_checks.hpp declarations.
9#include "neural_support.hpp" // Include neural_support.hpp declarations.
10#include "transformer_support.hpp" // Include transformer_support.hpp declarations.
11#include "fft_support.hpp" // Include fft_support.hpp declarations.
12#include "signal_filters.hpp" // Include signal_filters.hpp declarations.
13#include "lenet_support.hpp" // Build the complete seven-stage image classifier.
14#include <chrono> // Include chrono declarations.
15#include <cstdlib> // Include cstdlib declarations.
16#include <functional> // Include functional declarations.
17#include <future> // Include future declarations.
18#include <iomanip> // Include iomanip declarations.
19#include <sstream> // Include sstream declarations.
20#include <vector> // Include vector declarations.
21#ifdef SHDR_PLUGIN_PATH // Enable this branch when its feature macro is defined.
22#include <dlfcn.h> // Include dlfcn.h declarations.
23#endif // Finish this conditional compilation branch.
24
25namespace { // Group these helpers within their namespace.
26using namespace shdr::glsl; // Allow concise GLSL type and builtin names in this scope.
27const unsigned random_seed = 12345u; // Set random seed from this expression.
28
29unsigned setting(const char* name, unsigned fallback, unsigned maximum) { // Implement setting for the operations below.
30    const char* value = std::getenv(name); // Set value from this expression.
31    if (!value) // Check this condition before entering the following branch.
32        return fallback; // Return fallback.
33    char* end = 0; // Set end from this expression.
34    const unsigned long number = std::strtoul(value, &end, 10); // Set number from this expression.
35    if (!*value || *end || !number || number > maximum) // Check this condition before entering the following branch.
36        throw std::invalid_argument(std::string(name) + " must be between 1 and " + // Report  must be between 1 and .
37                                    std::to_string(maximum)); // Apply to string to these arguments.
38    return unsigned(number); // Return unsigned(number).
39} // End this block.
40
41shdr::extent groups(const shdr::kernel& kernel, unsigned x, unsigned y = 1, unsigned z = 1) { // Set y from this expression.
42    const shdr::extent local = kernel.local_size(); // Set local from this expression.
43    return shdr::extent( // Return shdr::extent(.
44        (x + local.x - 1) / local.x, (y + local.y - 1) / local.y, (z + local.z - 1) / local.z); // Continue the expression using x, local, x.
45} // End this block.
46
47void same(float actual, float expected, double absolute, double relative) { // Compare CPU and GPU values using the selected tolerance.
48    const double difference = std::fabs(double(actual) - double(expected)); // Set difference from this expression.
49    if (!std::isfinite(actual) || !std::isfinite(expected) || // Check this condition before entering the following branch.
50        difference > // Continue the expression using difference.
51            absolute + relative * std::max(std::fabs(double(actual)), std::fabs(double(expected)))) // Apply max to these arguments.
52        throw std::runtime_error("CPU/GPU float mismatch: " + std::to_string(actual) + " versus " + // Report CPU/GPU float mismatch: .
53                                 std::to_string(expected)); // Apply to string to these arguments.
54} // End this block.
55void same(unsigned actual, unsigned expected, double, double) { // Compare CPU and GPU values using the selected tolerance.
56    if (actual != expected) // Check this condition before entering the following branch.
57        throw std::runtime_error("CPU/GPU uint mismatch"); // Report CPU/GPU uint mismatch.
58} // End this block.
59template <class T, std::size_t N> // Accept the value types or dimensions used by this template.
60void same(const shdr::glsl::vec<T, N>& actual, // Compare CPU and GPU values using the selected tolerance.
61          const shdr::glsl::vec<T, N>& expected, // Declare expected for the following calculation.
62          double absolute, // Use this floating-point value or resource in the following calculation.
63          double relative) { // Use this floating-point value or resource in the following calculation.
64    for (std::size_t c = 0; c < N; ++c) // Iterate over c for this calculation.
65        same(actual[c], expected[c], absolute, relative); // Compare CPU and GPU values using the selected tolerance.
66} // End this block.
67
68struct comparison { // Declare comparison.
69    virtual ~comparison() { // Apply comparison to these arguments.
70    } // End this block.
71    virtual void capture() = 0; // Construct capture with these initial values.
72    virtual void verify() = 0; // Construct verify with these initial values.
73}; // Finish this declaration.
74template <class T> class output_check : public comparison { // Accept the value types or dimensions used by this template.
75    const shdr::buffer<T>& resource_; // Declare resource for the following calculation.
76    std::vector<T> expected_; // Declare expected for the following calculation.
77    double absolute_, relative_; // Use this floating-point value or resource in the following calculation.
78
79  public: // Set public access for the following class members.
80    explicit output_check(const shdr::buffer<T>& resource, // Declare output check with the parameters continued below.
81                          double absolute = 1e-4, // Use this floating-point value or resource in the following calculation.
82                          double relative = 1e-4) // Use this floating-point value or resource in the following calculation.
83        : resource_(resource), absolute_(absolute), relative_(relative) { // Implement resource for the operations below.
84    } // End this block.
85    void capture() override { // Retain a host copy of this CPU output after its timed execution.
86        const T* data = resource_.data(); // Set data from this expression.
87        expected_.assign(data, data + resource_.size()); // Copy the complete CPU reference while the resource remains unmodified.
88    } // End this block.
89    void verify() override { // Synchronize this GPU output and compare each element outside the timed section.
90        const T* data = resource_.data(); // Set data from this expression.
91        for (std::size_t i = 0; i < expected_.size(); ++i) { // Iterate over i for this calculation.
92            try { // Run the operation while retaining the error handling below.
93                same(data[i], expected_[i], absolute_, relative_); // Compare CPU and GPU values using the selected tolerance.
94            } catch (const std::exception& error) { // Report the exception from the preceding operation.
95                throw std::runtime_error(std::string(error.what()) + " at element " + // Report  at element .
96                                         std::to_string(i)); // Apply to string to these arguments.
97            } // End this block.
98        } // End this block.
99    } // End this block.
100}; // Finish this declaration.
101
102struct timing { // Declare timing.
103    std::string name, size; // Declare name for the following calculation.
104    double cpu, gpu; // Use this floating-point value or resource in the following calculation.
105}; // Finish this declaration.
106class benchmark { // Declare benchmark.
107    shdr::device* device_; // Declare device for the following calculation.
108    unsigned iterations_; // Declare iterations for the following calculation.
109    std::vector<timing> timings_; // Declare timings for the following calculation.
110    std::vector<std::string> metadata_; // Declare metadata for the following calculation.
111
112  public: // Set public access for the following class members.
113    benchmark(shdr::device* device, unsigned iterations) // Apply benchmark to these arguments.
114        : device_(device), iterations_(iterations) { // Implement device for the operations below.
115    } // End this block.
116    void note(const std::string& text) { // Implement note for the operations below.
117        metadata_.push_back(text); // Apply push back to these arguments.
118    } // End this block.
119    void measure(const std::string& name, // Measure one reusable pipeline after both backends have been warmed up.
120                 const std::string& size, // Declare size for the following calculation.
121                 const shdr::pipeline& pipeline, // Declare pipeline for the following calculation.
122                 std::initializer_list<comparison*> checks) { // Declare checks for the following calculation.
123        measure_operations( // Apply measure operations to these arguments.
124            name, // Continue the expression using name.
125            size, // Continue the expression using size.
126            [&pipeline](bool) { // Continue the expression using pipeline, bool.
127                pipeline.run(); // Execute the configured pipeline on the requested backend.
128            }, // Finish this declaration.
129            [&pipeline, this](bool) { // Continue the expression using pipeline, this, bool.
130                pipeline.run(*device_); // Execute the configured pipeline on the requested backend.
131            }, // Finish this declaration.
132            checks); // Continue the expression using checks.
133    } // End this block.
134    void measure_operations(const std::string& name, // Measure completed resident operations with reset and validation outside the timed loop.
135                            const std::string& size, // Declare size for the following calculation.
136                            const std::function<void(bool)>& cpu_operation, // Declare cpu operation for the following calculation.
137                            const std::function<void(bool)>& gpu_operation, // Declare gpu operation for the following calculation.
138                            std::initializer_list<comparison*> checks) { // Declare checks for the following calculation.
139        typedef std::chrono::steady_clock clock; // Measure elapsed time with the monotonic steady-clock counter.
140        cpu_operation(true); // Warm up CPU execution and reset stateful operations to their documented initial state.
141        clock::time_point begin = clock::now(); // Begin timing only after the CPU warmup has finished.
142        for (unsigned i = 0; i < iterations_; ++i) // Iterate over i for this calculation.
143            cpu_operation(false); // Run the next CPU operation with the state produced by its predecessor.
144        const double cpu = // Calculate the mean completed CPU operation time in milliseconds.
145            std::chrono::duration<double, std::milli>(clock::now() - begin).count() / iterations_; // Apply now to these arguments.
146        for (comparison* check : checks) // Visit each check required by this loop.
147            check->capture(); // Save the CPU outputs after timing so host comparison work is excluded.
148        double gpu = 0.0; // Initialize the GPU timing field for the optional device run.
149        if (device_) { // Check this condition before entering the following branch.
150            gpu_operation(true); // Compile, upload and warm up GPU resources before the timer starts.
151            device_->synchronize(); // Finish all warmup GPU commands before starting the timer.
152            begin = clock::now(); // Begin timing only after all warmup GPU commands have completed.
153            for (unsigned i = 0; i < iterations_; ++i) // Iterate over i for this calculation.
154                gpu_operation(false); // Run the next resident GPU operation before collecting final results.
155            device_->synchronize(); // Wait for the timed GPU commands to complete before stopping the timer.
156            gpu = std::chrono::duration<double, std::milli>(clock::now() - begin).count() / // Convert completed GPU elapsed time into milliseconds.
157                  iterations_; // Divide total GPU time by the number of complete operations.
158            for (comparison* check : checks) { // Visit each check required by this loop.
159                try { // Run the operation while retaining the error handling below.
160                    check->verify(); // Read outputs after timing and compare them with the retained CPU reference.
161                } catch (const std::exception& error) { // Report the exception from the preceding operation.
162                    throw std::runtime_error(name + ": " + error.what()); // Report : .
163                } // End this block.
164            } // End this block.
165        } // End this block.
166        timings_.push_back(timing{name, size, cpu, gpu}); // Retain the workload and both completed-operation timings for the results table.
167    } // End this block.
168    void print(unsigned scale) const { // Write the reproducible input settings, renderer and completed-operation timing table.
169        std::cout << "Seed: " << random_seed << ", iterations: " << iterations_ // Write this part of the example result or diagnostic.
170                  << ", scale: " << scale << ".\n"; // Write this part of the example result or diagnostic.
171        if (device_) // Check this condition before entering the following branch.
172            std::cout << "GPU renderer: " << device_->renderer() << ".\n"; // Write this part of the example result or diagnostic.
173        for (const std::string& text : metadata_) // Visit each std required by this loop.
174            std::cout << text << "\n"; // Write this part of the example result or diagnostic.
175        std::cout // Write this part of the example result or diagnostic.
176            << "Steady-state resident timings, compilation, first uploads and final readback excluded.\n" // Write this part of the example result or diagnostic.
177            << "Hybrid timings include transfers between CPU and GPU stages.\n\n" // Write this part of the example result or diagnostic.
178            << "| Pipeline | Workload | CPU ms | GPU ms | CPU/GPU |\n" // Write this part of the example result or diagnostic.
179            << "| --- | --- | ---: | ---: | ---: |\n" // Write this part of the example result or diagnostic.
180            << std::fixed << std::setprecision(3); // Write this part of the example result or diagnostic.
181        for (const timing& row : timings_) { // Visit each row required by this loop.
182            std::cout << "| " << row.name << " | " << row.size << " | " << row.cpu << " | "; // Write this part of the example result or diagnostic.
183            if (device_) // Check this condition before entering the following branch.
184                std::cout << row.gpu << " | " << row.cpu / row.gpu; // Write this part of the example result or diagnostic.
185            else // Print placeholders for the CPU execution mode.
186                std::cout << "- | -"; // Write this part of the example result or diagnostic.
187            std::cout << " |\n"; // Write this part of the example result or diagnostic.
188        } // End this block.
189    } // End this block.
190}; // Finish this declaration.
191
192void random_complex(shdr::buffer<vec2>& values, unsigned seed) { // Implement random complex for the operations below.
193    std::mt19937 generator(seed); // Construct generator with these initial values.
194    std::uniform_real_distribution<float> distribution(-1.0f, 1.0f); // Construct distribution with these initial values.
195    vec2* data = values.data(); // Set data from this expression.
196    for (std::size_t i = 0; i < values.size(); ++i) // Iterate over i for this calculation.
197        data[i] = vec2(distribution(generator), distribution(generator)); // Set data[i] from this expression.
198} // End this block.
199std::string dimensions(unsigned x, unsigned y, unsigned z = 0) { // Set z from this expression.
200    return std::to_string(x) + " x " + std::to_string(y) + (z ? " x " + std::to_string(z) : ""); // Return the result of this operation.
201} // End this block.
202
203void buffer_pipeline(benchmark& bench, unsigned count, bool hybrid, shdr::kernel* external = 0) { // Set external from this expression.
204    shdr::buffer<float> input(count), scaled(count), clipped(count), result(1); // Construct input with these initial values.
205    shdr_examples::randomize(input, random_seed, -4.0f, 12.0f); // Fill the input with reproducible random values.
206    auto owned_affine = // Set owned affine from this expression.
207        external ? std::unique_ptr<shdr::kernel>() : shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
208    shdr::kernel& affine = external ? *external : *owned_affine; // Set affine from this expression.
209    affine.bind("input_values", input) // Connect this shader port to its application resource.
210        .bind("output_values", scaled) // Connect this shader port to its application resource.
211        .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
212        .set<float>("scale", 2.0f) // Supply this shader uniform with its typed application value.
213        .set<float>("offset", 1.0f); // Supply this shader uniform with its typed application value.
214    auto clamp = shdr::make_kernel("clamp_kernel"); // Create the linked clamp kernel instance.
215    clamp->bind("input_values", scaled) // Connect this shader port to its application resource.
216        .bind("output_values", clipped) // Connect this shader port to its application resource.
217        .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
218        .set<float>("lower", 0.0f) // Supply this shader uniform with its typed application value.
219        .set<float>("upper", 8.0f); // Supply this shader uniform with its typed application value.
220    auto sum = shdr::make_kernel("sum_kernel"); // Create the linked sum kernel instance.
221    sum->bind("input_values", clipped).bind("output_values", result).set<unsigned>("count", count); // Connect this shader port to its application resource.
222    shdr::pipeline pipeline = // Set pipeline from this expression.
223        shdr::dispatch(affine, groups(affine, count)) >> // Add a shader dispatch with the required workgroup counts.
224        shdr::dispatch(*clamp, // Add a shader dispatch with the required workgroup counts.
225                       groups(*clamp, count), // Apply groups to these arguments.
226                       hybrid ? shdr::backend::cpu : shdr::backend::automatic) >> // Continue the expression using hybrid, shdr, backend.
227        shdr::dispatch(*sum, groups(*sum, 1)); // Add a shader dispatch with the required workgroup counts.
228    output_check<float> check_result(result), check_clipped(clipped); // Construct check result with these initial values.
229    bench.measure(hybrid     ? "Hybrid affine/clamp/sum" // Apply measure to these arguments.
230                  : external ? "Dynamic library stages" // Declare external for the following calculation.
231                             : "Buffer affine/clamp/sum", // Provide this text argument for the operation above.
232                  std::to_string(count) + " floats", // Apply to string to these arguments.
233                  pipeline, // Continue the expression using pipeline.
234                  {&check_result, &check_clipped}); // Continue the expression using check result, check clipped.
235} // End this block.
236
237void image_pipeline(benchmark& bench, // Declare image pipeline with the parameters continued below.
238                    unsigned side, // Declare side for the following calculation.
239                    shdr::device* device = 0, // Set device from this expression.
240                    bool borrow = false) { // Set borrow from this expression.
241    shdr::buffer<float> input(side * side), output(side * side); // Construct input with these initial values.
242    shdr_examples::randomize(input, random_seed, 0.0f, 1.0f); // Fill the input with reproducible random values.
243    // The application retains ownership of a texture temporarily borrowed by shdr.
244    shdr::image2d<float, 1> application_image(side, side), filtered(side, side); // Construct application image with these initial values.
245    shdr::image2d<float, 1> original = // Set original from this expression.
246        borrow && device ? shdr::image2d<float, 1>::from_gl( // Apply from gl to these arguments.
247                               *device, application_image.gl_handle(*device), side, side) // Apply gl handle to these arguments.
248                         : application_image; // Declare application image for the following calculation.
249    auto into = shdr::make_kernel("buffer_to_image_kernel"); // Create the linked buffer to image kernel instance.
250    into->bind("input_values", input).bind("output_image", original); // Connect this shader port to its application resource.
251    auto blur = shdr::make_kernel("blur_kernel"); // Create the linked blur kernel instance.
252    blur->bind("input_image", original).bind("output_image", filtered); // Connect this shader port to its application resource.
253    auto out = shdr::make_kernel("image_to_buffer_kernel"); // Create the linked image to buffer kernel instance.
254    out->bind("input_image", filtered).bind("output_values", output); // Connect this shader port to its application resource.
255    shdr::pipeline pipeline = shdr::dispatch(*into, groups(*into, side, side)) >> // Add a shader dispatch with the required workgroup counts.
256                              shdr::dispatch(*blur, groups(*blur, side, side)) >> // Add a shader dispatch with the required workgroup counts.
257                              shdr::dispatch(*out, groups(*out, side, side)); // Add a shader dispatch with the required workgroup counts.
258    output_check<float> check(output); // Construct check with these initial values.
259    bench.measure(borrow ? "Borrowed image blur" : "Buffer/image blur", // Apply measure to these arguments.
260                  dimensions(side, side), // Apply dimensions to these arguments.
261                  pipeline, // Continue the expression using pipeline.
262                  {&check}); // Continue the expression using check.
263} // End this block.
264
265void bayer_pipeline(benchmark& bench, unsigned side) { // Implement bayer pipeline for the operations below.
266    const unsigned count = side * side, words = (count / 4u * 5u + 3u) / 4u; // Set count from this expression.
267    shdr::buffer<unsigned> packed(words), samples(count); // Construct packed with these initial values.
268    shdr::buffer<vec4> colors(count); // Construct colors with these initial values.
269    shdr::buffer<float> gray(count); // Construct gray with these initial values.
270    std::mt19937 generator(random_seed); // Construct generator with these initial values.
271    for (unsigned i = 0; i < words; ++i) // Iterate over i for this calculation.
272        packed.data()[i] = generator(); // Apply data to these arguments.
273    auto unpack = shdr::make_kernel("bayer_unpack_kernel"); // Create the linked bayer unpack kernel instance.
274    unpack->bind("packed_words", packed) // Connect this shader port to its application resource.
275        .bind("samples", samples) // Connect this shader port to its application resource.
276        .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
277        .set<unsigned>("packing", 2u) // Supply this shader uniform with its typed application value.
278        .set<unsigned>("bit_depth", 10u); // Supply this shader uniform with its typed application value.
279    auto debayer = shdr::make_kernel("debayer_kernel"); // Create the linked debayer kernel instance.
280    debayer->bind("samples", samples) // Connect this shader port to its application resource.
281        .bind("colors", colors) // Connect this shader port to its application resource.
282        .set<unsigned>("width", side) // Supply this shader uniform with its typed application value.
283        .set<unsigned>("height", side) // Supply this shader uniform with its typed application value.
284        .set<unsigned>("pattern", 0u) // Supply this shader uniform with its typed application value.
285        .set<unsigned>("bit_depth", 10u); // Supply this shader uniform with its typed application value.
286    auto luminance = shdr::make_kernel("luminance_kernel"); // Create the linked luminance kernel instance.
287    luminance->bind("colors", colors).bind("gray", gray).set<unsigned>("count", count); // Connect this shader port to its application resource.
288    shdr::pipeline pipeline = shdr::dispatch(*unpack, groups(*unpack, count)) >> // Add a shader dispatch with the required workgroup counts.
289                              shdr::dispatch(*debayer, groups(*debayer, side, side)) >> // Add a shader dispatch with the required workgroup counts.
290                              shdr::dispatch(*luminance, groups(*luminance, count)); // Add a shader dispatch with the required workgroup counts.
291    output_check<unsigned> sample_check(samples); // Construct sample check with these initial values.
292    output_check<vec4> color_check(colors); // Construct color check with these initial values.
293    output_check<float> gray_check(gray); // Construct gray check with these initial values.
294    bench.measure("RAW10/debayer/luminance", // Apply measure to these arguments.
295                  dimensions(side, side), // Apply dimensions to these arguments.
296                  pipeline, // Continue the expression using pipeline.
297                  {&sample_check, &color_check, &gray_check}); // Continue the expression using sample check, color check, gray check.
298} // End this block.
299
300void evt3_pipeline(benchmark& bench, unsigned word_count) { // Implement evt3 pipeline for the operations below.
301    shdr_examples::evt3_decoder decoder(word_count); // Construct decoder with these initial values.
302    decoder.load(shdr_examples::evt3_random_words(word_count, random_seed)); // Apply load to these arguments.
303    output_check<uvec4> event_check(decoder.events), state_check(decoder.state); // Construct event check with these initial values.
304    output_check<unsigned> offset_check(decoder.offsets); // Construct offset check with these initial values.
305    bench.measure("EVT3 event decoding", // Apply measure to these arguments.
306                  std::to_string(word_count) + " packed words", // Apply to string to these arguments.
307                  decoder.reset_pipeline(), // Apply reset pipeline to these arguments.
308                  {&event_check, &state_check, &offset_check}); // Continue the expression using event check, state check, offset check.
309    bench.note("EVT3 compact output: " + std::to_string(decoder.event_count()) + " events from " + // Apply note to these arguments.
310               std::to_string(word_count) + " words, five stages include state reset."); // Apply to string to these arguments.
311} // End this block.
312
313void evt3_optical_flow(benchmark& bench) { // Implement evt3 optical flow for the operations below.
314    const std::vector<std::uint16_t> plane = shdr_examples::evt3_planar_words(32, 24); // Set plane from this expression.
315    std::vector<std::uint16_t> words; // Declare words for the following calculation.
316    std::mt19937 random(random_seed); // Construct random with these initial values.
317    for (std::size_t i = 0; i < plane.size(); i += 4) { // Iterate over i for this calculation.
318        // Seeded spatial sampling varies neighborhoods while preserving the known temporal plane.
319        if (random() % 5u == 0u) // Check this condition before entering the following branch.
320            continue; // Skip the remaining work for this loop iteration.
321        words.insert(words.end(), plane.begin() + i, plane.begin() + i + 4); // Apply insert to these arguments.
322    } // End this block.
323    shdr_examples::evt3_decoder decoder(static_cast<unsigned>(words.size())); // Construct decoder with these initial values.
324    decoder.load(words); // Apply load to these arguments.
325    shdr::buffer<vec4> flow(decoder.events.size()); // Construct flow with these initial values.
326    auto shader = shdr_examples::evt3_flow_kernel(decoder, flow, 64u); // Set shader from this expression.
327    shdr::pipeline pipeline = decoder.reset_pipeline(); // Set pipeline from this expression.
328    pipeline += shdr::dispatch(*shader, groups(*shader, static_cast<unsigned>(flow.size()))); // Add a shader dispatch with the required workgroup counts.
329    output_check<uvec4> event_check(decoder.events); // Construct event check with these initial values.
330    output_check<unsigned> offset_check(decoder.offsets); // Construct offset check with these initial values.
331    output_check<vec4> flow_check(flow, 1e-3, 1e-3); // Construct flow check with these initial values.
332    bench.measure("EVT3 + optical flow", // Apply measure to these arguments.
333                  "32 x 24 sampled plane, history 64", // Provide this text argument for the operation above.
334                  pipeline, // Continue the expression using pipeline.
335                  {&event_check, &offset_check, &flow_check}); // Continue the expression using event check, offset check, flow check.
336    bench.note("Optical flow input: " + std::to_string(decoder.event_count()) + // Apply note to these arguments.
337               " seeded samples on a known temporal plane, six linked stages."); // Provide this text argument for the operation above.
338} // End this block.
339
340void binary_neural_pipeline(benchmark& bench, shdr::device* device, bool convolutional) { // Implement binary neural pipeline for the operations below.
341    shdr_examples::binary_network model(convolutional, 32); // Construct model with these initial values.
342    const std::string name = convolutional ? "CNN" : "ANN"; // Set name from this expression.
343    const std::string size = std::to_string(model.batch_count()) + " samples, " + // Set size from this expression.
344                             std::to_string(model.parameter_count()) + " parameters"; // Apply to string to these arguments.
345    output_check<float> parameter_check(model.parameters), gradient_check(model.gradients); // Construct parameter check with these initial values.
346    bench.measure_operations( // Apply measure operations to these arguments.
347        name + " training", // Provide this text argument for the operation above.
348        size, // Continue the expression using size.
349        [&](bool warmup) { // Continue the expression using bool, warmup.
350            if (warmup) // Check this condition before entering the following branch.
351                model.reset_parameters(); // Restore the same initial parameter vector before this backend's warmup update.
352            model.training_pipeline().run(); // Compute all sample gradients and apply one averaged SGD parameter update.
353        }, // Finish this declaration.
354        [&](bool warmup) { // Continue the expression using bool, warmup.
355            if (warmup) // Check this condition before entering the following branch.
356                model.reset_parameters(); // Restore the same initial parameter vector before this backend's warmup update.
357            model.training_pipeline().run(*device); // Compute all sample gradients and apply one averaged SGD parameter update.
358        }, // Finish this declaration.
359        {&parameter_check, &gradient_check}); // Continue the expression using parameter check, gradient check.
360    output_check<float> prediction_check(model.predictions); // Construct prediction check with these initial values.
361    bench.measure(name + " inference", size, model.prediction_pipeline(), {&prediction_check}); // Apply measure to these arguments.
362} // End this block.
363
364void transformer_pipeline(benchmark& bench, shdr::device* device) { // Implement transformer pipeline for the operations below.
365    shdr_examples::transformer_model model(8); // Construct model with these initial values.
366    model.reset_inputs(random_seed); // Apply reset inputs to these arguments.
367    const std::string size = "8 sequences, 4 tokens x 4, 134 parameters"; // Set size from this expression.
368    output_check<float> parameter_check(model.parameters, 1e-4, 1e-3), // Declare parameter check with the parameters continued below.
369        gradient_check(model.gradients, 1e-4, 1e-3); // Apply gradient check to these arguments.
370    bench.measure_operations( // Apply measure operations to these arguments.
371        "Transformer training", // Provide this text argument for the operation above.
372        size, // Continue the expression using size.
373        [&](bool warmup) { // Continue the expression using bool, warmup.
374            if (warmup) // Check this condition before entering the following branch.
375                model.reset_parameters(random_seed); // Restore the same initial parameter vector before this backend's warmup update.
376            model.training_pipeline().run(); // Compute all sample gradients and apply one averaged SGD parameter update.
377        }, // Finish this declaration.
378        [&](bool warmup) { // Continue the expression using bool, warmup.
379            if (warmup) // Check this condition before entering the following branch.
380                model.reset_parameters(random_seed); // Restore the same initial parameter vector before this backend's warmup update.
381            model.training_pipeline().run(*device); // Compute all sample gradients and apply one averaged SGD parameter update.
382        }, // Finish this declaration.
383        {&parameter_check, &gradient_check}); // Continue the expression using parameter check, gradient check.
384    output_check<vec4> prediction_check(model.predictions, 1e-4, 1e-3); // Construct prediction check with these initial values.
385    bench.measure("Transformer inference", size, model.prediction_pipeline(), {&prediction_check}); // Apply measure to these arguments.
386    bench.note( // Apply note to these arguments.
387        "Neural training timings measure one mini-batch gradient/SGD update, inference measures the complete forward pipeline."); // Provide this text argument for the operation above.
388} // End this block.
389
390void fft_round_trip(benchmark& bench) { // Implement fft round trip for the operations below.
391    const unsigned length = 1024, channels = 8; // Set length from this expression.
392    shdr::buffer<vec2> input(length * channels), spectrum(length * channels), // Declare input with the parameters continued below.
393        output(length * channels); // Apply output to these arguments.
394    random_complex(input, random_seed); // Apply random complex to these arguments.
395    shdr_examples::fft1d forward(input, spectrum, length, channels); // Construct forward with these initial values.
396    shdr_examples::fft1d inverse(spectrum, output, length, channels, true); // Construct inverse with these initial values.
397    shdr::pipeline pipeline = forward.processing_pipeline(); // Set pipeline from this expression.
398    inverse.append_to(pipeline); // Apply append to to these arguments.
399    output_check<vec2> spectrum_check(spectrum, 1e-4, 1e-3), output_comparison(output, 1e-4, 1e-3); // Construct spectrum check with these initial values.
400    bench.measure( // Apply measure to these arguments.
401        "FFT forward/inverse", "8 x 1024 complex", pipeline, {&spectrum_check, &output_comparison}); // Provide this text argument for the operation above.
402    const shdr::buffer<vec2>& read_input = input; // Use a constant buffer view so GPU writes synchronize before host reads.
403    const shdr::buffer<vec2>& read_output = output; // Use a constant buffer view so GPU writes synchronize before host reads.
404    for (unsigned i = 0; i < length * channels; ++i) // Iterate over i for this calculation.
405        same(read_output.data()[i], read_input.data()[i], 1e-4, 1e-3); // Compare CPU and GPU values using the selected tolerance.
406} // End this block.
407
408void fft_band_pass(benchmark& bench) { // Implement fft band pass for the operations below.
409    const unsigned length = 1024, channels = 8; // Set length from this expression.
410    shdr::buffer<vec2> input(length * channels), output(length * channels); // Construct input with these initial values.
411    random_complex(input, random_seed); // Apply random complex to these arguments.
412    shdr_examples::fft_filter filter(input, // Declare filter with the parameters continued below.
413                                     output, // Continue the expression using output.
414                                     length, // Continue the expression using length.
415                                     channels, // Continue the expression using channels.
416                                     48000.0f, // Complete this expression or declaration.
417                                     shdr_examples::frequency_filter::band_pass, // Continue the expression using shdr examples, frequency filter, band pass.
418                                     4000.0f, // Complete this expression or declaration.
419                                     10000.0f); // Complete this expression or declaration.
420    output_check<vec2> check(output, 1e-4, 1e-3); // Construct check with these initial values.
421    bench.measure( // Apply measure to these arguments.
422        "FFT band-pass", "8 x 1024 complex, 4-10 kHz", filter.processing_pipeline(), {&check}); // Apply processing pipeline to these arguments.
423} // End this block.
424
425void fir_low_pass(benchmark& bench) { // Implement fir low pass for the operations below.
426    const unsigned count = 2048, channels = 8; // Set count from this expression.
427    shdr::buffer<float> input(count * channels), output(count * channels); // Construct input with these initial values.
428    shdr_examples::randomize(input, random_seed, -1.0f, 1.0f); // Fill the input with reproducible random values.
429    shdr_examples::fir_pipeline filter(input, // Declare filter with the parameters continued below.
430                                       output, // Continue the expression using output.
431                                       count, // Continue the expression using count.
432                                       channels, // Continue the expression using channels.
433                                       shdr_examples::filter_mode::low_pass, // Continue the expression using shdr examples, filter mode, low pass.
434                                       48000.0, // Complete this expression or declaration.
435                                       4000.0, // Complete this expression or declaration.
436                                       0.0, // Complete this expression or declaration.
437                                       65); // Complete this expression or declaration.
438    output_check<float> check(output); // Construct check with these initial values.
439    bench.measure( // Apply measure to these arguments.
440        "FIR low-pass", "8 x 2048 floats, 65 taps", filter.processing_pipeline(), {&check}); // Apply processing pipeline to these arguments.
441} // End this block.
442
443void biquad_low_pass(benchmark& bench, shdr::device* device) { // Implement biquad low pass for the operations below.
444    const unsigned count = 2048, channels = 8; // Set count from this expression.
445    shdr::buffer<float> input(count * channels), output(count * channels); // Construct input with these initial values.
446    shdr_examples::randomize(input, random_seed, -1.0f, 1.0f); // Fill the input with reproducible random values.
447    shdr_examples::biquad_pipeline filter( // Declare filter with the parameters continued below.
448        input, output, count, channels, shdr_examples::filter_mode::low_pass, 48000.0, 4000.0); // Continue the expression using input, output, count.
449    output_check<float> output_comparison(output); // Construct output comparison with these initial values.
450    output_check<vec2> state_comparison(filter.state); // Construct state comparison with these initial values.
451    bench.measure_operations( // Apply measure operations to these arguments.
452        "Biquad low-pass", // Provide this text argument for the operation above.
453        "8 x 2048 floats, persistent state", // Provide this text argument for the operation above.
454        [&](bool warmup) { // Continue the expression using bool, warmup.
455            if (warmup) // Check this condition before entering the following branch.
456                filter.reset_state(); // Restore identical zero-delay state before this backend processes its first signal block.
457            filter.processing_pipeline().run(); // Execute the configured pipeline on the requested backend.
458        }, // Finish this declaration.
459        [&](bool warmup) { // Continue the expression using bool, warmup.
460            if (warmup) // Check this condition before entering the following branch.
461                filter.reset_state(); // Restore identical zero-delay state before this backend processes its first signal block.
462            filter.processing_pipeline().run(*device); // Execute the configured pipeline on the requested backend.
463        }, // Finish this declaration.
464        {&output_comparison, &state_comparison}); // Continue the expression using output comparison, state comparison.
465} // End this block.
466
467/// Compare completed asynchronous batches, with readback confined to warmup validation.
468void async_pipeline(benchmark& bench, shdr::device* device) { // Implement async pipeline for the operations below.
469    const unsigned count = 257, frame_count = 32, slot_count = 3; // Set count from this expression.
470    struct slot { // Declare slot.
471        shdr::buffer<float> input, output; // Declare input for the following calculation.
472        std::future<void> cpu_done; // Declare cpu done for the following calculation.
473        shdr::submission gpu_done; // Declare gpu done for the following calculation.
474        bool occupied; // Declare occupied for the following calculation.
475        unsigned frame; // Declare frame for the following calculation.
476        slot() : input(count), output(count), occupied(false), frame(0) { // Apply slot to these arguments.
477        } // End this block.
478    }; // Finish this declaration.
479    std::vector<slot> slots(slot_count); // Allocate three disjoint input and output slots before measuring the ring.
480    for (unsigned i = 0; i < slot_count; ++i) // Iterate over i for this calculation.
481        shdr_examples::randomize(slots[i].input, random_seed + i, -1.0f, 1.0f); // Fill the input with reproducible random values.
482    auto shader = shdr::make_kernel("affine_kernel"); // Create the linked affine kernel instance.
483    shader->set<unsigned>("count", count); // Supply this shader uniform with its typed application value.
484    shdr::pipeline pipeline = shdr::dispatch(*shader, groups(*shader, count)); // Add a shader dispatch with the required workgroup counts.
485    shdr::executor workers(2, slot_count); // Use two CPU workers with enough bounded queue capacity for the three live slots.
486    if (device) { // Check this condition before entering the following branch.
487        for (slot& resource : slots) { // Visit each resource required by this loop.
488            shader->bind("input_values", resource.input).bind("output_values", resource.output); // Connect this shader port to its application resource.
489            pipeline.prepare(*device); // Prepare all slot resources and compile the GPU program before ring timing.
490        } // End this block.
491    } // End this block.
492    const auto execute = [&](bool gpu, bool validate) { // Set execute from this expression.
493        unsigned produced = 0, completed = 0; // Set produced from this expression.
494        while (completed < frame_count) { // Pump the three resource slots until the complete frame batch finishes.
495            bool progress = false; // Set progress from this expression.
496            for (unsigned index = 0; index < slot_count; ++index) { // Iterate over index for this calculation.
497                slot& resource = slots[index]; // Set resource from this expression.
498                if (resource.occupied) { // Check this condition before entering the following branch.
499                    const bool ready = // Set ready from this expression.
500                        gpu ? resource.gpu_done.ready() // Apply ready to these arguments.
501                            : resource.cpu_done.wait_for(std::chrono::nanoseconds(0)) == // Declare resource for the following calculation.
502                                  std::future_status::ready; // Continue the expression using std, future status, ready.
503                    if (!ready) // Keep this slot occupied until its preceding operation actually finishes.
504                        continue; // Skip the remaining work for this loop iteration.
505                    if (!gpu) // Check this condition before entering the following branch.
506                        resource.cpu_done.get(); // Observe CPU worker exceptions before considering this slot reusable.
507                    if (validate) { // Check every completed warmup frame while keeping readback outside the timed ring.
508                        const shdr::buffer<float>& input = resource.input; // Use a constant buffer view so GPU writes synchronize before host reads.
509                        const shdr::buffer<float>& output = resource.output; // Use a constant buffer view so GPU writes synchronize before host reads.
510                        for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
511                            same(output.data()[i], // Compare CPU and GPU values using the selected tolerance.
512                                 input.data()[i] * float(resource.frame + 1) - // Apply data to these arguments.
513                                     float(resource.frame), // Apply float to these arguments.
514                                 1e-4, // Complete this expression or declaration.
515                                 1e-4); // Complete this expression or declaration.
516                    } // End this block.
517                    resource.occupied = false; // Release this slot only after its prior frame has completed.
518                    ++completed; // Advance the completed-frame counter after observing its fence or future.
519                    progress = true; // Set progress from this expression.
520                } // End this block.
521                if (produced < frame_count && produced % slot_count == index) { // Assign frames round-robin so both backends receive the same slot inputs.
522                    resource.frame = produced++; // Record the frame identity before changing its shader uniforms.
523                    shader->bind("input_values", resource.input) // Connect this shader port to its application resource.
524                        .bind("output_values", resource.output) // Connect this shader port to its application resource.
525                        .set<float>("scale", float(resource.frame + 1)) // Supply this shader uniform with its typed application value.
526                        .set<float>("offset", -float(resource.frame)); // Supply this shader uniform with its typed application value.
527                    if (gpu) // Check this condition before entering the following branch.
528                        resource.gpu_done = pipeline.submit(*device); // Queue the frame on GPU and retain its zero-timeout completion fence.
529                    else // Queue the frame snapshot on the CPU worker pool.
530                        resource.cpu_done = workers.submit(pipeline); // Queue a snapshot of the frame bindings for the bounded CPU executor.
531                    resource.occupied = true; // Protect this slot from reuse until its submission or future is ready.
532                    progress = true; // Set progress from this expression.
533                } // End this block.
534            } // End this block.
535            if (gpu) // Check this condition before entering the following branch.
536                device->collect_garbage(); // Poll and retire completed GPU fence records on the producer thread.
537            if (!progress) // Check this condition before entering the following branch.
538                std::this_thread::yield(); // Yield the CPU when all live slots are still busy.
539        } // End this block.
540    }; // Finish this declaration.
541    output_check<float> a(slots[0].output), b(slots[1].output), c(slots[2].output); // Construct a with these initial values.
542    bench.measure_operations( // Apply measure operations to these arguments.
543        "Async ring", // Provide this text argument for the operation above.
544        "32 x 257 floats, 3 slots", // Provide this text argument for the operation above.
545        [&](bool validate) { // Continue the expression using bool, validate.
546            execute(false, validate); // Apply execute to these arguments.
547        }, // Finish this declaration.
548        [&](bool validate) { // Continue the expression using bool, validate.
549            execute(true, validate); // Apply execute to these arguments.
550        }, // Finish this declaration.
551        {&a, &b, &c}); // Continue the expression using a, b, c.
552    workers.shutdown(); // Apply shutdown to these arguments.
553} // End this block.
554
555void convolution_2d(benchmark& bench, unsigned side) { // Implement convolution 2d for the operations below.
556    shdr::buffer<vec2> input(side * side), output(side * side); // Construct input with these initial values.
557    shdr::buffer<float> coefficients(25); // Construct coefficients with these initial values.
558    random_complex(input, random_seed); // Apply random complex to these arguments.
559    shdr_examples::randomize(coefficients, random_seed + 1, -0.04f, 0.04f); // Fill the input with reproducible random values.
560    auto convolution = shdr::make_kernel("convolution2d_kernel"); // Create the linked convolution2d kernel instance.
561    convolution->bind("input_values", input) // Connect this shader port to its application resource.
562        .bind("output_values", output) // Connect this shader port to its application resource.
563        .bind("coefficients", coefficients) // Connect this shader port to its application resource.
564        .set<uvec2>("dimensions", uvec2(side, side)) // Supply this shader uniform with its typed application value.
565        .set<uvec2>("kernel_size", uvec2(5, 5)); // Supply this shader uniform with its typed application value.
566    shdr::pipeline pipeline; // Declare pipeline for the following calculation.
567    pipeline += shdr::dispatch(*convolution, groups(*convolution, side, side)); // Add a shader dispatch with the required workgroup counts.
568    output_check<vec2> check(output); // Construct check with these initial values.
569    bench.measure("2D convolution 5 x 5", dimensions(side, side) + " vec2", pipeline, {&check}); // Apply measure to these arguments.
570} // End this block.
571
572void convolution_3d(benchmark& bench, unsigned side) { // Implement convolution 3d for the operations below.
573    shdr::buffer<float> input(side * side * side), output(side * side * side), coefficients(27); // Construct input with these initial values.
574    shdr_examples::randomize(input, random_seed, -1.0f, 1.0f); // Fill the input with reproducible random values.
575    shdr_examples::randomize(coefficients, random_seed + 1, -1.0f / 27, 1.0f / 27); // Fill the input with reproducible random values.
576    auto convolution = shdr::make_kernel("convolution3d_kernel"); // Create the linked convolution3d kernel instance.
577    convolution->bind("input_values", input) // Connect this shader port to its application resource.
578        .bind("output_values", output) // Connect this shader port to its application resource.
579        .bind("coefficients", coefficients) // Connect this shader port to its application resource.
580        .set<uvec3>("dimensions", uvec3(side, side, side)) // Supply this shader uniform with its typed application value.
581        .set<uvec3>("kernel_size", uvec3(3, 3, 3)); // Supply this shader uniform with its typed application value.
582    shdr::pipeline pipeline; // Declare pipeline for the following calculation.
583    pipeline += shdr::dispatch(*convolution, groups(*convolution, side, side, side)); // Add a shader dispatch with the required workgroup counts.
584    output_check<float> check(output); // Construct check with these initial values.
585    bench.measure("3D convolution 3 x 3 x 3", dimensions(side, side, side), pipeline, {&check}); // Apply measure to these arguments.
586} // End this block.
587
588void fourier_pipeline(benchmark& bench) { // Implement fourier pipeline for the operations below.
589    const unsigned side = 32, // Set side from this expression.
590                   count = side * side; // Direct DFT remains small even when other workloads scale.
591    shdr::buffer<vec2> input(count), rows(count), spectrum(count), inverse_rows(count), // Declare input with the parameters continued below.
592        restored(count); // Apply restored to these arguments.
593    random_complex(input, random_seed); // Apply random complex to these arguments.
594    auto forward_rows = shdr::make_kernel("dft_rows_kernel"); // Create the linked dft rows kernel instance.
595    auto forward_columns = shdr::make_kernel("dft_columns_kernel"); // Create the linked dft columns kernel instance.
596    auto reverse_rows = shdr::make_kernel("dft_rows_kernel"); // Create the linked dft rows kernel instance.
597    auto reverse_columns = shdr::make_kernel("dft_columns_kernel"); // Create the linked dft columns kernel instance.
598    shdr::kernel* kernels[] = { // Declare kernels for the following calculation.
599        forward_rows.get(), forward_columns.get(), reverse_rows.get(), reverse_columns.get()}; // Apply get to these arguments.
600    shdr::buffer<vec2>* inputs[] = {&input, &rows, &spectrum, &inverse_rows}; // Declare inputs for the following calculation.
601    shdr::buffer<vec2>* outputs[] = {&rows, &spectrum, &inverse_rows, &restored}; // Declare outputs for the following calculation.
602    shdr::pipeline pipeline; // Declare pipeline for the following calculation.
603    for (unsigned i = 0; i < 4; ++i) { // Iterate over i for this calculation.
604        kernels[i] // Continue the expression using kernels, i.
605            ->bind("input_values", *inputs[i]) // Connect this shader port to its application resource.
606            .bind("output_values", *outputs[i]) // Connect this shader port to its application resource.
607            .set<uvec2>("dimensions", uvec2(side, side)) // Supply this shader uniform with its typed application value.
608            .set<float>("direction", i < 2 ? -1.0f : 1.0f) // Supply this shader uniform with its typed application value.
609            .set<float>("normalization", i < 2 ? 1.0f : 1.0f / side); // Supply this shader uniform with its typed application value.
610        pipeline += shdr::dispatch(*kernels[i], groups(*kernels[i], side, side)); // Add a shader dispatch with the required workgroup counts.
611    } // End this block.
612    output_check<vec2> spectral_check(spectrum, 1e-3, 1e-3), // Declare spectral check with the parameters continued below.
613        reconstruction_check(restored, 1e-3, 1e-3); // Apply reconstruction check to these arguments.
614    bench.measure("Forward/inverse direct DFT", // Apply measure to these arguments.
615                  dimensions(side, side) + " complex", // Apply dimensions to these arguments.
616                  pipeline, // Continue the expression using pipeline.
617                  {&spectral_check, &reconstruction_check}); // Continue the expression using spectral check, reconstruction check.
618    const shdr::buffer<vec2>& read_input = input; // Use a constant buffer view so GPU writes synchronize before host reads.
619    const shdr::buffer<vec2>& read_restored = restored; // Use a constant buffer view so GPU writes synchronize before host reads.
620    for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
621        same(read_restored.data()[i], read_input.data()[i], 1e-3, 1e-3); // Compare CPU and GPU values using the selected tolerance.
622} // End this block.
623
624void vision_pipeline(benchmark& bench, unsigned side) { // Implement vision pipeline for the operations below.
625    const unsigned count = side * side; // Set count from this expression.
626    shdr::buffer<float> input(count), horizontal(count), smooth(count), response(count); // Construct input with these initial values.
627    shdr::buffer<vec2> gradient(count); // Construct gradient with these initial values.
628    shdr::buffer<vec4> tensor(count); // Construct tensor with these initial values.
629    shdr::buffer<unsigned> peaks(count); // Construct peaks with these initial values.
630    shdr_examples::randomize(input, random_seed, 0.0f, 1.0f); // Fill the input with reproducible random values.
631    auto h = shdr::make_kernel("gaussian_horizontal_kernel"), // Create the linked gaussian horizontal kernel instance.
632         v = shdr::make_kernel("gaussian_vertical_kernel"); // Create the linked gaussian vertical kernel instance.
633    auto sobel = shdr::make_kernel("sobel_kernel"), // Create the linked sobel kernel instance.
634         outer = shdr::make_kernel("harris_tensor_kernel"); // Create the linked harris tensor kernel instance.
635    auto harris = shdr::make_kernel("harris_response_kernel"), // Create the linked harris response kernel instance.
636         nonmax = shdr::make_kernel("nonmax_kernel"); // Create the linked nonmax kernel instance.
637    h->bind("input_values", input).bind("output_values", horizontal); // Connect this shader port to its application resource.
638    v->bind("input_values", horizontal).bind("output_values", smooth); // Connect this shader port to its application resource.
639    sobel->bind("input_values", smooth).bind("output_values", gradient); // Connect this shader port to its application resource.
640    outer->bind("input_values", gradient) // Connect this shader port to its application resource.
641        .bind("output_values", tensor) // Connect this shader port to its application resource.
642        .set<unsigned>("count", count); // Supply this shader uniform with its typed application value.
643    harris->bind("input_values", tensor) // Connect this shader port to its application resource.
644        .bind("output_values", response) // Connect this shader port to its application resource.
645        .set<float>("harris_k", 0.04f); // Supply this shader uniform with its typed application value.
646    nonmax->bind("input_values", response) // Connect this shader port to its application resource.
647        .bind("output_values", peaks) // Connect this shader port to its application resource.
648        .set<float>("threshold", 1e-8f); // Supply this shader uniform with its typed application value.
649    shdr::kernel* kernels[] = { // Declare kernels for the following calculation.
650        h.get(), v.get(), sobel.get(), outer.get(), harris.get(), nonmax.get()}; // Apply get to these arguments.
651    shdr::pipeline pipeline; // Declare pipeline for the following calculation.
652    for (unsigned i = 0; i < 6; ++i) { // Iterate over i for this calculation.
653        if (i != 3) // Check this condition before entering the following branch.
654            kernels[i]->set<unsigned>("width", side).set<unsigned>("height", side); // Supply this shader uniform with its typed application value.
655        pipeline += shdr::dispatch(*kernels[i], groups(*kernels[i], count)); // Add a shader dispatch with the required workgroup counts.
656    } // End this block.
657    output_check<float> smooth_check(smooth), response_check(response); // Construct smooth check with these initial values.
658    output_check<vec2> gradient_check(gradient); // Construct gradient check with these initial values.
659    output_check<unsigned> peak_check(peaks); // Construct peak check with these initial values.
660    bench.measure("Gaussian/Sobel/Harris/NMS", // Apply measure to these arguments.
661                  dimensions(side, side), // Apply dimensions to these arguments.
662                  pipeline, // Continue the expression using pipeline.
663                  {&smooth_check, &gradient_check, &response_check, &peak_check}); // Continue the expression using smooth check, gradient check, response check.
664} // End this block.
665
666void lenet_pipeline(benchmark& bench) { // Compare all seven layers of the named image classifier.
667    shdr_examples::lenet_classifier model(32); // Allocate a resident batch of thirty-two grayscale images.
668    shdr_examples::randomize(model.inputs, random_seed, 0.0f, 1.0f); // Use normalized seeded image values.
669    shdr_examples::randomize(model.parameters, random_seed + 1u, -0.025f, 0.025f); // Keep randomized learned parameters small enough for stable comparisons.
670    output_check<float> convolution_check(model.conv2, 1e-4, 1e-3); // Check the second convolution features after each backend completes.
671    output_check<float> hidden_check(model.hidden2, 1e-4, 1e-3); // Check the final hidden representation as well as the head.
672    output_check<float> logits_check(model.logits, 1e-4, 1e-3); // Retain the CPU reference for every class score.
673    bench.measure("LeNet classifier", // Identify this full convolutional model in the timing table.
674                  "32 x 28 x 28, 61,706 parameters", // State the complete resident workload size.
675                  model.inference_pipeline(), // Execute two convolution and pool pairs followed by three dense stages.
676                  {&convolution_check, &hidden_check, &logits_check}); // Verify internal features and final scores outside timing.
677} // Finish the complete classifier comparison.
678
679void compression_pipeline(benchmark& bench, unsigned count) { // Implement compression pipeline for the operations below.
680    const unsigned block_size = 256, blocks = (count + block_size - 1) / block_size; // Set block size from this expression.
681    shdr::buffer<unsigned> input(count), sizes(blocks), offsets(blocks + 1), encoded(count * 2), // Declare input with the parameters continued below.
682        restored(count); // Apply restored to these arguments.
683    std::mt19937 generator(random_seed); // Construct generator with these initial values.
684    std::uniform_int_distribution<unsigned> run_length(4, 16), value(0, 4095); // Construct run length with these initial values.
685    unsigned position = 0; // Set position from this expression.
686    while (position < count) { // Fill the input with seeded runs until the requested element count is reached.
687        const unsigned run = run_length(generator), sample = value(generator); // Set run from this expression.
688        for (unsigned i = 0; i < run && position < count; ++i) // Iterate over i for this calculation.
689            input.data()[position++] = sample; // Apply data to these arguments.
690    } // End this block.
691    auto tally = shdr::make_kernel("rle_count_kernel"), // Create the linked rle count kernel instance.
692         prefix = shdr::make_kernel("rle_offsets_kernel"); // Create the linked rle offsets kernel instance.
693    auto encode = shdr::make_kernel("rle_encode_kernel"), // Create the linked rle encode kernel instance.
694         decode = shdr::make_kernel("rle_decode_kernel"); // Create the linked rle decode kernel instance.
695    tally->bind("values", input) // Connect this shader port to its application resource.
696        .bind("sizes", sizes) // Connect this shader port to its application resource.
697        .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
698        .set<unsigned>("block_size", block_size); // Supply this shader uniform with its typed application value.
699    prefix->bind("sizes", sizes).bind("offsets", offsets).set<unsigned>("blocks", blocks); // Connect this shader port to its application resource.
700    encode->bind("values", input) // Connect this shader port to its application resource.
701        .bind("offsets", offsets) // Connect this shader port to its application resource.
702        .bind("encoded", encoded) // Connect this shader port to its application resource.
703        .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
704        .set<unsigned>("block_size", block_size); // Supply this shader uniform with its typed application value.
705    decode->bind("encoded", encoded) // Connect this shader port to its application resource.
706        .bind("offsets", offsets) // Connect this shader port to its application resource.
707        .bind("restored", restored) // Connect this shader port to its application resource.
708        .set<unsigned>("count", count) // Supply this shader uniform with its typed application value.
709        .set<unsigned>("block_size", block_size); // Supply this shader uniform with its typed application value.
710    shdr::pipeline pipeline = shdr::dispatch(*tally, groups(*tally, blocks)) >> // Add a shader dispatch with the required workgroup counts.
711                              shdr::dispatch(*prefix, groups(*prefix, 1)) >> // Add a shader dispatch with the required workgroup counts.
712                              shdr::dispatch(*encode, groups(*encode, blocks)) >> // Add a shader dispatch with the required workgroup counts.
713                              shdr::dispatch(*decode, groups(*decode, blocks)); // Add a shader dispatch with the required workgroup counts.
714    output_check<unsigned> restored_check(restored), offsets_check(offsets), encoded_check(encoded); // Construct restored check with these initial values.
715    bench.measure("Lossless tiled RLE round trip", // Apply measure to these arguments.
716                  std::to_string(count) + " uints", // Apply to string to these arguments.
717                  pipeline, // Continue the expression using pipeline.
718                  {&restored_check, &offsets_check, &encoded_check}); // Continue the expression using restored check, offsets check, encoded check.
719    const shdr::buffer<unsigned>& read_input = input; // Use a constant buffer view so GPU writes synchronize before host reads.
720    const shdr::buffer<unsigned>& read_restored = restored; // Use a constant buffer view so GPU writes synchronize before host reads.
721    for (unsigned i = 0; i < count; ++i) // Iterate over i for this calculation.
722        same(read_restored.data()[i], read_input.data()[i], 0, 0); // Compare CPU and GPU values using the selected tolerance.
723    const shdr::buffer<unsigned>& read_offsets = offsets; // Use a constant buffer view so GPU writes synchronize before host reads.
724    const unsigned stream_words = read_offsets.data()[blocks]; // Set stream words from this expression.
725    std::ostringstream metadata; // Declare metadata for the following calculation.
726    metadata << "Lossless RLE: " << stream_words << " payload words + " << blocks + 1 // Provide this text argument for the operation above.
727             << " offset words, " << std::fixed << std::setprecision(2) // Write this part of the example result or diagnostic.
728             << double(count) / double(stream_words + blocks + 1) << "x size reduction."; // Write this part of the example result or diagnostic.
729    bench.note(metadata.str()); // Apply note to these arguments.
730} // End this block.
731
732#ifdef SHDR_PLUGIN_PATH // Enable this branch when its feature macro is defined.
733void dynamic_pipeline(benchmark& bench, unsigned count) { // Implement dynamic pipeline for the operations below.
734    void* library = dlopen(SHDR_PLUGIN_PATH, RTLD_NOW | RTLD_LOCAL); // Set library from this expression.
735    if (!library) // Check this condition before entering the following branch.
736        throw std::runtime_error(dlerror()); // Report this failed operation.
737    typedef shdr::kernel* (*create_function)(); // Declare shdr for the following calculation.
738    typedef void (*destroy_function)(shdr::kernel*); // Construct void with these initial values.
739    create_function create = // Set create from this expression.
740        reinterpret_cast<create_function>(dlsym(library, "shdr_create_kernel")); // Apply dlsym to these arguments.
741    destroy_function destroy = // Set destroy from this expression.
742        reinterpret_cast<destroy_function>(dlsym(library, "shdr_destroy_kernel")); // Apply dlsym to these arguments.
743    if (!create || !destroy) { // Check this condition before entering the following branch.
744        dlclose(library); // Apply dlclose to these arguments.
745        throw std::runtime_error("incomplete benchmark plugin"); // Report incomplete benchmark plugin.
746    } // End this block.
747    shdr::kernel* shader = 0; // Set shader from this expression.
748    try { // Run the operation while retaining the error handling below.
749        shader = create(); // Set shader from this expression.
750        if (!shader) // Check this condition before entering the following branch.
751            throw std::runtime_error("benchmark plugin returned no shader"); // Report an empty result from the plugin factory.
752        buffer_pipeline(bench, count, false, shader); // Apply buffer pipeline to these arguments.
753        destroy(shader); // Apply destroy to these arguments.
754        shader = 0; // Set shader from this expression.
755        dlclose(library); // Apply dlclose to these arguments.
756    } catch (...) { // Report the exception from the preceding operation.
757        if (shader) // Check this condition before entering the following branch.
758            destroy(shader); // Apply destroy to these arguments.
759        dlclose(library); // Apply dlclose to these arguments.
760        throw; // Report this failed operation.
761    } // End this block.
762} // End this block.
763#endif // Finish this conditional compilation branch.
764} // namespace
765
766int main(int argc, char** argv) { // Run this example and validate its results.
767    try { // Run the operation while retaining the error handling below.
768        shdr_examples::runner run(argc, argv); // Construct run with these initial values.
769        const unsigned iterations = setting("SHDR_BENCH_ITERATIONS", 3, 1000); // Set iterations from this expression.
770        const unsigned scale = setting("SHDR_BENCH_SCALE", 1, 4); // Set scale from this expression.
771        const unsigned side = 128 * scale; // Set side from this expression.
772        benchmark bench(run.device(), iterations); // Construct bench with these initial values.
773        buffer_pipeline(bench, side * side, false); // Apply buffer pipeline to these arguments.
774        image_pipeline(bench, side); // Apply image pipeline to these arguments.
775        image_pipeline(bench, side, run.device(), true); // Apply image pipeline to these arguments.
776        buffer_pipeline(bench, side * side, true); // Apply buffer pipeline to these arguments.
777#ifdef SHDR_PLUGIN_PATH // Enable this branch when its feature macro is defined.
778        dynamic_pipeline(bench, side * side); // Apply dynamic pipeline to these arguments.
779#endif // Finish this conditional compilation branch.
780        bayer_pipeline(bench, side); // Apply bayer pipeline to these arguments.
781        convolution_2d(bench, side); // Apply convolution 2d to these arguments.
782        convolution_3d(bench, 24 * scale); // Apply convolution 3d to these arguments.
783        fourier_pipeline(bench); // Apply fourier pipeline to these arguments.
784        vision_pipeline(bench, side); // Apply vision pipeline to these arguments.
785        compression_pipeline(bench, 65536 * scale); // Apply compression pipeline to these arguments.
786        evt3_pipeline(bench, 65536 * scale); // Apply evt3 pipeline to these arguments.
787        async_pipeline(bench, run.device()); // Apply async pipeline to these arguments.
788        evt3_optical_flow(bench); // Apply evt3 optical flow to these arguments.
789        binary_neural_pipeline(bench, run.device(), false); // Apply binary neural pipeline to these arguments.
790        binary_neural_pipeline(bench, run.device(), true); // Apply binary neural pipeline to these arguments.
791        transformer_pipeline(bench, run.device()); // Apply transformer pipeline to these arguments.
792        fft_round_trip(bench); // Apply fft round trip to these arguments.
793        fir_low_pass(bench); // Apply fir low pass to these arguments.
794        biquad_low_pass(bench, run.device()); // Apply biquad low pass to these arguments.
795        fft_band_pass(bench); // Apply fft band pass to these arguments.
796        lenet_pipeline(bench); // Compare the named image classifier on both backends.
797        bench.print(scale); // Apply print to these arguments.
798        return 0; // Report successful completion.
799#ifdef SHDR_HAVE_EGL // Enable this branch when its feature macro is defined.
800    } catch (const shdr_examples::context_unavailable& error) { // Report the exception from the preceding operation.
801        std::cerr << "SKIP: " << error.what() << "\n"; // Write this part of the example result or diagnostic.
802        return 77; // Report an unavailable optional GPU backend.
803#endif // Finish this conditional compilation branch.
804    } catch (const std::exception& error) { // Report the exception from the preceding operation.
805        std::cerr << error.what() << "\n"; // Write this part of the example result or diagnostic.
806        return 1; // Report a failed check or operation.
807    } // End this block.
808} // End this block.