Source browser
examples/signal_filters.hpp
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 signal_filters.hpp
2 * @brief Causal FIR and stateful biquad examples over ordinary float buffers.
3 * Frequencies use the same units as the sample rate, normally hertz. Buffers
4 * store complete channels consecutively: channel*count+sample. Filter classes
5 * expose pipelines that can be linked directly to other shdr dispatches.
6 */
7#ifndef SHDR_EXAMPLES_SIGNAL_FILTERS_HPP // Include this example helper only once.
8#define SHDR_EXAMPLES_SIGNAL_FILTERS_HPP // Remember that this example helper is available.
9#include "shdr.h" // Use the same header for CPU and GPU resource wiring.
10#include <algorithm> // Use standard copying, limits and zero filling.
11#include <array> // Use fixed-size host coefficient and prediction arrays.
12#include <cmath> // Use ordinary scalar math in host designers and oracles.
13#include <vector> // Use owned host arrays for coefficients and reference results.
14
15namespace shdr_examples { // Keep these example helpers in their own namespace.
16
17/** @brief Frequency-selective modes shared by the FIR and biquad designers. */
18enum class filter_mode { low_pass, high_pass, band_pass, band_stop, moving_average }; // Name the supported filter shapes explicitly.
19
20namespace signal_detail { // Keep these example helpers in their own namespace.
21inline double pi() { // Return the angle constant used by the filter designers.
22 return 3.1415926535897932384626433832795; // Use a double-precision angle constant.
23} // Finish the pi operation.
24inline void sample_rate(double rate) { // Require a finite positive sampling rate.
25 if (!std::isfinite(rate) || rate <= 0.0) // Reject a nonfinite or out-of-range value.
26 throw shdr::error("filter sample rate must be finite and positive"); // Report the invalid design or failed check clearly.
27} // Finish the sample_rate operation.
28inline void cutoff(double frequency, double rate) { // Require a frequency strictly below Nyquist.
29 if (!std::isfinite(frequency) || frequency <= 0.0 || frequency >= rate / 2.0) // Reject a nonfinite or out-of-range value.
30 throw shdr::error("filter frequency must be strictly between zero and Nyquist"); // Report the invalid design or failed check clearly.
31} // Finish the cutoff operation.
32inline std::vector<double> low_pass(double normalized, unsigned taps) { // Build a Hamming-windowed sinc with unit DC gain.
33 std::vector<double> result(taps); // Keep filter design coefficients in double precision until normalization.
34 const int center = static_cast<int>(taps / 2u); // Find the symmetry center of the odd-length FIR.
35 double sum = 0.0; // Start the scalar accumulator at zero.
36 for (unsigned i = 0; i < taps; ++i) { // Use only causal taps that lie inside this batch.
37 const int offset = static_cast<int>(i) - center; // Measure this tap relative to the symmetry center.
38 const double sinc = offset ? std::sin(2.0 * pi() * normalized * offset) / (pi() * offset) // Evaluate the ideal low-pass sinc around the center tap.
39 : 2.0 * normalized; // Use the finite sinc limit at its center tap.
40 const double window = 0.54 - 0.46 * std::cos(2.0 * pi() * i / (taps - 1u)); // Apply the Hamming window to reduce sidelobes.
41 sum += result[i] = sinc * window; // Store the windowed tap and accumulate its DC gain.
42 } // Finish this channel, sample or feature pass.
43 if (!std::isfinite(sum) || std::fabs(sum) < 1e-12) // Reject a nonfinite or out-of-range value.
44 throw shdr::error("FIR design has negligible normalization gain"); // Report the invalid design or failed check clearly.
45 for (double& value : result) // Apply the design adjustment to every coefficient.
46 value /= sum; // Normalize this tap by the computed passband gain.
47 return result; // Return the finite designed filter coefficients.
48} // Finish the low_pass operation.
49inline void normalize(std::vector<double>& values, double normalized) { // Set the requested passband gain to one.
50 double gain = 0.0; // Start the scalar accumulator at zero.
51 const int center = static_cast<int>(values.size() / 2u); // Find the symmetry center of the odd-length FIR.
52 for (unsigned i = 0; i < values.size(); ++i) // Visit every value needed by this calculation.
53 gain += values[i] * std::cos(2.0 * pi() * normalized * (static_cast<int>(i) - center)); // Sum the zero-phase response at the selected frequency.
54 if (!std::isfinite(gain) || std::fabs(gain) < 1e-12) // Reject a nonfinite or out-of-range value.
55 throw shdr::error("FIR design has negligible normalization gain"); // Report the invalid design or failed check clearly.
56 for (double& value : values) // Apply the design adjustment to every coefficient.
57 value /= gain; // Normalize this tap by the computed passband gain.
58} // Finish the normalize operation.
59inline void validate_biquad(const std::array<float, 5>& coefficients) { // Reject nonfinite or unstable float coefficients.
60 for (float value : coefficients) // Inspect each stored float coefficient.
61 if (!std::isfinite(value)) // Reject a nonfinite or out-of-range value.
62 throw shdr::error("biquad coefficients must be finite"); // Report the invalid design or failed check clearly.
63 const double a1 = coefficients[3], a2 = coefficients[4]; // Read the normalized feedback coefficients in double precision.
64 if (std::fabs(a2) >= 1.0 || 1.0 + a1 + a2 <= 0.0 || 1.0 - a1 + a2 <= 0.0) // Handle this validation or boundary case explicitly.
65 throw shdr::error("biquad denominator must remain stable at float precision"); // Report the invalid design or failed check clearly.
66} // Finish the validate_biquad operation.
67inline unsigned channels(unsigned value) { // Validate the number of independent channels.
68 if (!value || value > 65536u) // Handle this validation or boundary case explicitly.
69 throw shdr::error("filter channel count must be between 1 and 65536"); // Report the invalid design or failed check clearly.
70 return value; // Return the validated channel count.
71} // Finish the channels operation.
72inline void buffers(const shdr::buffer<float>& input, // Validate channel-major storage and prevent unsafe aliasing.
73 const shdr::buffer<float>& output, // Receive distinct output storage with matching channel capacity.
74 unsigned count, // Receive the validated sample, channel or coefficient count.
75 unsigned channel_count) { // Receive the validated sample, channel or coefficient count.
76 if (!count || count > 0xffffffffu / channel_count) // Handle this validation or boundary case explicitly.
77 throw shdr::error("filter count must be positive and fit GLSL uint indexing"); // Report the invalid design or failed check clearly.
78 const std::size_t needed = static_cast<std::size_t>(count) * channel_count; // Use host-size arithmetic to count every channel sample.
79 if (input.size() < needed || output.size() < needed) // Check that both buffers contain every channel sample.
80 throw shdr::error("filter buffers do not contain all channel samples"); // Report the invalid design or failed check clearly.
81 if (input.state() == output.state()) // Reject input and output that share the same storage.
82 throw shdr::error("filter input and output must use distinct storage"); // Report the invalid design or failed check clearly.
83} // Finish the buffers operation.
84} // namespace signal_detail
85
86/** @brief Design an odd-length linear-phase FIR using a Hamming-windowed sinc.
87 * Low-pass coefficients have unit DC gain. High-pass uses spectral inversion
88 * and unit Nyquist gain. Band-pass subtracts two low-pass designs and has unit
89 * midpoint gain. band-stop inverts that band-pass and has unit DC gain.
90 * Moving-average returns equal weights and ignores both cutoffs. Other modes
91 * require cutoffs strictly inside Nyquist. band edges must increase. Tap count
92 * must be odd and between 3 and 4095. The causal group delay is (taps-1)/2.
93 */
94inline std::vector<float> fir_coefficients(filter_mode mode, // Design the requested causal linear-phase FIR.
95 double sample_rate, // Receive the positive sampling rate in frequency units.
96 double cutoff1, // Receive the cutoff or band-edge frequency.
97 double cutoff2 = 0.0, // Receive the cutoff or band-edge frequency.
98 unsigned taps = 31u) { // Receive the validated sample, channel or coefficient count.
99 signal_detail::sample_rate(sample_rate); // Validate the sample-rate units before designing coefficients.
100 if (taps < 3u || taps > 4095u || taps % 2u == 0u) // Handle this validation or boundary case explicitly.
101 throw shdr::error("FIR tap count must be odd and between 3 and 4095"); // Report the invalid design or failed check clearly.
102 if (mode == filter_mode::moving_average) // Choose the coefficient rule for this filter shape.
103 return std::vector<float>(taps, 1.0f / taps); // Give every moving-average tap the same weight.
104 signal_detail::cutoff(cutoff1, sample_rate); // Validate this frequency against the sampling rate.
105 std::vector<double> values; // Keep filter design coefficients in double precision until normalization.
106 if (mode == filter_mode::low_pass || mode == filter_mode::high_pass) { // Choose the coefficient rule for this filter shape.
107 values = signal_detail::low_pass(cutoff1 / sample_rate, taps); // Build the appropriate low-pass prototype.
108 if (mode == filter_mode::high_pass) { // Choose the coefficient rule for this filter shape.
109 for (double& value : values) // Apply the design adjustment to every coefficient.
110 value = -value; // Invert this coefficient for spectral inversion.
111 values[taps / 2u] += 1.0; // Add the centered impulse to complete spectral inversion.
112 signal_detail::normalize(values, 0.5); // Scale the chosen passband response to unity.
113 } // Finish handling this condition.
114 } else if (mode == filter_mode::band_pass || mode == filter_mode::band_stop) { // Use the alternative filter-design case.
115 signal_detail::cutoff(cutoff2, sample_rate); // Validate this frequency against the sampling rate.
116 if (cutoff1 >= cutoff2) // Require the lower band edge to precede the upper edge.
117 throw shdr::error("FIR band edges must be strictly increasing"); // Report the invalid design or failed check clearly.
118 const std::vector<double> lower = signal_detail::low_pass(cutoff1 / sample_rate, taps); // Keep the lower-edge low-pass prototype for subtraction.
119 values = signal_detail::low_pass(cutoff2 / sample_rate, taps); // Build the appropriate low-pass prototype.
120 for (unsigned i = 0; i < taps; ++i) // Use only causal taps that lie inside this batch.
121 values[i] -= lower[i]; // Subtract the lower low-pass response to form a band-pass.
122 signal_detail::normalize(values, 0.5 * (cutoff1 / sample_rate + cutoff2 / sample_rate)); // Scale the chosen passband response to unity.
123 if (mode == filter_mode::band_stop) { // Choose the coefficient rule for this filter shape.
124 for (double& value : values) // Apply the design adjustment to every coefficient.
125 value = -value; // Invert this coefficient for spectral inversion.
126 values[taps / 2u] += 1.0; // Add the centered impulse to complete spectral inversion.
127 signal_detail::normalize(values, 0.0); // Scale the chosen passband response to unity.
128 } // Finish handling this condition.
129 } else // Use the alternative filter-design case.
130 throw shdr::error("unknown FIR filter mode"); // Report the invalid design or failed check clearly.
131 std::vector<float> result(values.begin(), values.end()); // Convert the designed coefficients to GPU-compatible floats.
132 for (float value : result) // Inspect each stored float coefficient.
133 if (!std::isfinite(value)) // Reject a nonfinite or out-of-range value.
134 throw shdr::error("FIR coefficients must remain finite at float precision"); // Report the invalid design or failed check clearly.
135 return result; // Return the finite designed filter coefficients.
136} // Finish handling this condition.
137
138/** @brief Design a normalized RBJ/W3C low-pass, high-pass, band-pass or notch biquad.
139 * Return order is b0,b1,b2,a1,a2 with a0 normalized to one. Band-pass has unit
140 * center-frequency gain. band_stop selects a notch. Frequency is the cutoff or
141 * center in sample-rate units, and Q must be finite and positive. Designs whose
142 * rounded float feedback coefficients lose strict stability are rejected.
143 * Formula reference: https://www.w3.org/TR/audio-eq-cookbook/ .
144 */
145inline std::array<float, 5> biquad_coefficients(filter_mode mode, // Design normalized coefficients from the RBJ formulas.
146 double sample_rate, // Receive the positive sampling rate in frequency units.
147 double frequency, // Receive the tone, cutoff or center frequency.
148 double q = 0.7071067811865475244) { // Receive the positive biquad quality factor.
149 signal_detail::sample_rate(sample_rate); // Validate the sample-rate units before designing coefficients.
150 signal_detail::cutoff(frequency, sample_rate); // Validate this frequency against the sampling rate.
151 if (!std::isfinite(q) || q <= 0.0) // Reject a nonfinite or out-of-range value.
152 throw shdr::error("biquad Q must be finite and positive"); // Report the invalid design or failed check clearly.
153 const double omega = 2.0 * signal_detail::pi() * (frequency / sample_rate); // Convert the cutoff frequency into radians per sample.
154 const double cosine = std::cos(omega), alpha = std::sin(omega) / (2.0 * q), a0 = 1.0 + alpha; // Compute the RBJ frequency and damping terms.
155 double b0 = 0.0, b1 = 0.0, b2 = 0.0; // Prepare the three feed-forward numerator coefficients.
156 if (mode == filter_mode::low_pass) { // Choose the coefficient rule for this filter shape.
157 b0 = b2 = (1.0 - cosine) / 2.0; // Use symmetric outer numerator coefficients for this filter.
158 b1 = 1.0 - cosine; // Set the middle numerator coefficient for this filter.
159 } else if (mode == filter_mode::high_pass) { // Use the alternative filter-design case.
160 b0 = b2 = (1.0 + cosine) / 2.0; // Use symmetric outer numerator coefficients for this filter.
161 b1 = -(1.0 + cosine); // Set the middle numerator coefficient for this filter.
162 } else if (mode == filter_mode::band_pass) { // Use the alternative filter-design case.
163 b0 = alpha; // Give the band-pass unit gain at its center frequency.
164 b2 = -alpha; // Use the opposite outer coefficient for the band-pass zero.
165 } else if (mode == filter_mode::band_stop) { // Use the alternative filter-design case.
166 b0 = b2 = 1.0; // Use symmetric outer numerator coefficients for this filter.
167 b1 = -2.0 * cosine; // Set the middle numerator coefficient for this filter.
168 } else // Use the alternative filter-design case.
169 throw shdr::error("biquad supports low-pass, high-pass, band-pass and band-stop modes"); // Report the invalid design or failed check clearly.
170 const std::array<float, 5> result = {{static_cast<float>(b0 / a0), // Normalize every coefficient by a0 before storing floats.
171 static_cast<float>(b1 / a0), // Convert the computed host value to the shader float type.
172 static_cast<float>(b2 / a0), // Convert the computed host value to the shader float type.
173 static_cast<float>(-2.0 * cosine / a0), // Convert the computed host value to the shader float type.
174 static_cast<float>((1.0 - alpha) / a0)}}; // Convert the computed host value to the shader float type.
175 signal_detail::validate_biquad(result); // Require stable finite feedback coefficients.
176 return result; // Return the finite designed filter coefficients.
177} // Finish handling this condition.
178
179/** @brief One causal batch FIR dispatch over channel-major float resources.
180 * Each execution starts every channel with an implicit zero prefix.
181 * Repeating the pipeline processes a fresh batch. Input and output
182 * require distinct storage. Public coefficients can be edited before
183 * the next dispatch, retaining the original coefficient count.
184 */
185class fir_pipeline { // Bundle FIR coefficients and one causal batch dispatch.
186 unsigned channels_; // Remember the fixed number of independent signal channels.
187 std::unique_ptr<shdr::kernel> shader_; // Own the compiled filter instance and its resource bindings.
188 shdr::pipeline pipeline_; // Keep the filter dispatch ready for repeated execution.
189
190 public: // Expose the resources and processing helpers.
191 shdr::buffer<float> coefficients; // Keep filter coefficients in a resource shared by CPU and GPU.
192 /** @brief Bind a batch and copy a nonempty set of finite causal FIR weights. */
193 fir_pipeline(const shdr::buffer<float>& input, // Own coefficients and connect the FIR batch resources.
194 const shdr::buffer<float>& output, // Receive distinct output storage with matching channel capacity.
195 unsigned count, // Receive the validated sample, channel or coefficient count.
196 unsigned channels, // Receive the validated sample, channel or coefficient count.
197 const std::vector<float>& weights) // Use the caller-provided finite FIR coefficients.
198 : channels_(signal_detail::channels(channels)), // Validate the channel count before allocating per-channel state.
199 shader_(shdr::make_kernel("signal_fir_kernel")), coefficients(weights.size()) { // Create the compiled native filter instance.
200 if (weights.empty() || weights.size() > 4095u) // Handle this validation or boundary case explicitly.
201 throw shdr::error("FIR weights require between 1 and 4095 coefficients"); // Report the invalid design or failed check clearly.
202 for (float value : weights) // Inspect each stored float coefficient.
203 if (!std::isfinite(value)) // Reject a nonfinite or out-of-range value.
204 throw shdr::error("FIR weights must be finite"); // Report the invalid design or failed check clearly.
205 std::copy(weights.begin(), weights.end(), coefficients.data()); // Copy these host values into the shared resource.
206 shader_->bind("coefficients", coefficients) // Connect the shader to its coefficient, state or sample resource.
207 .set<unsigned>("taps", static_cast<unsigned>(weights.size())) // Set the shader dimensions or scalar processing parameter.
208 .set<unsigned>("channels", channels_); // Set the shader dimensions or scalar processing parameter.
209 bind(input, output, count); // Connect the initial input and output batches.
210 } // Finish the fir_pipeline operation.
211 /** @brief Design coefficients and bind their batch pipeline in one step. */
212 fir_pipeline(const shdr::buffer<float>& input, // Own coefficients and connect the FIR batch resources.
213 const shdr::buffer<float>& output, // Receive distinct output storage with matching channel capacity.
214 unsigned count, // Receive the validated sample, channel or coefficient count.
215 unsigned channels, // Receive the validated sample, channel or coefficient count.
216 filter_mode mode, // Receive the requested filter shape.
217 double rate, // Receive the positive sampling rate in frequency units.
218 double cutoff1, // Receive the cutoff or band-edge frequency.
219 double cutoff2 = 0.0, // Receive the cutoff or band-edge frequency.
220 unsigned taps = 31u) // Receive the validated sample, channel or coefficient count.
221 : fir_pipeline(input, // Reuse the constructor with newly designed coefficients.
222 output, // Pass the distinct output resource to the wiring constructor.
223 count, // Pass the positive per-channel sample count.
224 channels, // Pass the fixed number of independent channels.
225 fir_coefficients(mode, rate, cutoff1, cutoff2, taps)) { // Design the causal FIR coefficients for this configuration.
226 } // Finish the fir_coefficients operation.
227 /** @brief Rebind channel-major batches and update their sample count. */
228 void bind(const shdr::buffer<float>& input, const shdr::buffer<float>& output, unsigned count) { // Connect the next input and output block.
229 signal_detail::buffers(input, output, count, channels_); // Validate dimensions and distinct storage before rebinding.
230 shader_->bind("input_values", input) // Connect the shader to its coefficient, state or sample resource.
231 .bind("output_values", output) // Connect the next named resource to the same shader instance.
232 .set<unsigned>("count", count); // Set the shader dimensions or scalar processing parameter.
233 const unsigned total = count * channels_; // Remember the fixed number of independent signal channels.
234 pipeline_ = shdr::pipeline( // Retain the dispatch with the required work group count.
235 shdr::dispatch(*shader_, shdr::extent(total / 64u + (total % 64u ? 1u : 0u)))); // Cover each independent FIR sample or biquad channel with a work group.
236 } // Finish the bind operation.
237 /** @brief Return the prepared wiring for CPU run(), GPU run(device) or async submission. */
238 const shdr::pipeline& processing_pipeline() const { // Expose the reusable filter processing dispatch.
239 return pipeline_; // Keep the filter dispatch ready for repeated execution.
240 } // Finish the processing_pipeline operation.
241}; // Finish the fir_pipeline resource helper.
242
243/** @brief One stateful biquad dispatch, parallel across independent channels.
244 * Each channel processes its temporal block sequentially. State contains one
245 * vec2(z1,z2) per channel for transposed direct-form II. State
246 * persists across bind() and runs, including explicit CPU/GPU transitions.
247 * Give independent streams or concurrent jobs separate filter instances/state.
248 */
249class biquad_pipeline { // Bundle biquad coefficients and persistent channel history.
250 unsigned channels_; // Remember the fixed number of independent signal channels.
251 std::unique_ptr<shdr::kernel> shader_; // Own the compiled filter instance and its resource bindings.
252 shdr::pipeline pipeline_; // Keep the filter dispatch ready for repeated execution.
253
254 public: // Expose the resources and processing helpers.
255 shdr::buffer<float> coefficients; // Keep filter coefficients in a resource shared by CPU and GPU.
256 shdr::buffer<shdr::glsl::vec2> state; // Keep one pair of recurrence values for each channel.
257 /** @brief Bind normalized stable coefficients and zero-initialized channel state. */
258 biquad_pipeline(const shdr::buffer<float>& input, // Own coefficients and connect the persistent biquad state.
259 const shdr::buffer<float>& output, // Receive distinct output storage with matching channel capacity.
260 unsigned count, // Receive the validated sample, channel or coefficient count.
261 unsigned channels, // Receive the validated sample, channel or coefficient count.
262 const std::array<float, 5>& weights) // Use five normalized biquad coefficients.
263 : channels_(signal_detail::channels(channels)), // Validate the channel count before allocating per-channel state.
264 shader_(shdr::make_kernel("signal_biquad_kernel")), coefficients(5), state(channels_) { // Create the compiled native filter instance.
265 signal_detail::validate_biquad(weights); // Require stable finite feedback coefficients.
266 std::copy(weights.begin(), weights.end(), coefficients.data()); // Copy these host values into the shared resource.
267 shader_->bind("coefficients", coefficients) // Connect the shader to its coefficient, state or sample resource.
268 .bind("state", state) // Connect the next named resource to the same shader instance.
269 .set<unsigned>("channels", channels_); // Set the shader dimensions or scalar processing parameter.
270 bind(input, output, count); // Connect the initial input and output batches.
271 pipeline_ = shdr::pipeline( // Retain the dispatch with the required work group count.
272 shdr::dispatch(*shader_, shdr::extent(channels_ / 64u + (channels_ % 64u ? 1u : 0u)))); // Cover each independent FIR sample or biquad channel with a work group.
273 } // Finish the biquad_pipeline operation.
274 /** @brief Design coefficients and bind their streaming pipeline in one step. */
275 biquad_pipeline(const shdr::buffer<float>& input, // Own coefficients and connect the persistent biquad state.
276 const shdr::buffer<float>& output, // Receive distinct output storage with matching channel capacity.
277 unsigned count, // Receive the validated sample, channel or coefficient count.
278 unsigned channels, // Receive the validated sample, channel or coefficient count.
279 filter_mode mode, // Receive the requested filter shape.
280 double rate, // Receive the positive sampling rate in frequency units.
281 double frequency, // Receive the tone, cutoff or center frequency.
282 double q = 0.7071067811865475244) // Receive the positive biquad quality factor.
283 : biquad_pipeline( // Reuse the constructor with newly designed coefficients.
284 input, output, count, channels, biquad_coefficients(mode, rate, frequency, q)) { // Reuse the resource wiring with freshly designed normalized coefficients.
285 } // Finish the biquad_coefficients operation.
286 /** @brief Rebind the next channel-major block, preserving every channel's state. */
287 void bind(const shdr::buffer<float>& input, const shdr::buffer<float>& output, unsigned count) { // Connect the next input and output block.
288 signal_detail::buffers(input, output, count, channels_); // Validate dimensions and distinct storage before rebinding.
289 shader_->bind("input_values", input) // Connect the shader to its coefficient, state or sample resource.
290 .bind("output_values", output) // Connect the next named resource to the same shader instance.
291 .set<unsigned>("count", count); // Set the shader dimensions or scalar processing parameter.
292 } // Finish the bind operation.
293 /** @brief Explicitly discard channel history and clear its host state.
294 * Resetting writes zero values to host state. The next GPU dispatch
295 * uploads the cleared state through the ordinary resource synchronization.
296 */
297 void reset_state() { // Replace the channel history with zero-filled host state.
298 state.mark_host_modified(); // Discard old GPU history before clearing every host state value.
299 shdr::glsl::vec2* values = state.data(); // Acquire the host storage that this initialization will overwrite.
300 std::fill(values, values + state.size(), shdr::glsl::vec2(0.0f)); // Clear every stored channel recurrence value.
301 } // Finish the reset_state operation.
302 /** @brief Return the state-preserving CPU/GPU processing dispatch. */
303 const shdr::pipeline& processing_pipeline() const { // Expose the reusable filter processing dispatch.
304 return pipeline_; // Keep the filter dispatch ready for repeated execution.
305 } // Finish the processing_pipeline operation.
306}; // Finish the biquad_pipeline resource helper.
307} // namespace shdr_examples
308#endif // Finish the conditional compilation block.