Skip to main content

librashader_runtime/
binding.rs

1use crate::parameters::RuntimeParameters;
2use crate::uniforms::{BindUniform, NoUniformBinder, UniformStorage};
3use bit_set::BitSet;
4use librashader_common::map::{FastHashMap, ShortString};
5use librashader_common::Size;
6use librashader_preprocess::ShaderParameter;
7use librashader_reflect::reflect::semantics::{
8    BindingMeta, MemberOffset, Semantic, TextureBinding, TextureSemantics, UniformBinding,
9    UniformMeta, UniqueSemantics,
10};
11use num_traits::Zero;
12use std::ops::{Deref, DerefMut};
13
14/// Trait for input textures used during uniform binding,
15pub trait TextureInput {
16    /// Gets the size of this input texture.
17    fn size(&self) -> Size<u32>;
18}
19
20/// A uniform member offset with context that needs to be resolved.
21pub trait ContextOffset<H, C, D = ()>
22where
23    H: BindUniform<C, f32, D>,
24    H: BindUniform<C, u32, D>,
25    H: BindUniform<C, i32, D>,
26    H: for<'a> BindUniform<C, &'a [f32; 3], D>,
27    H: for<'a> BindUniform<C, &'a [f32; 4], D>,
28    H: for<'a> BindUniform<C, &'a [f32; 16], D>,
29{
30    /// Gets the `MemberOffset` part of the offset.
31    fn offset(&self) -> MemberOffset;
32
33    /// Gets the context part of the offset.
34    fn context(&self) -> C;
35}
36
37impl<D, H> ContextOffset<H, Option<()>, D> for MemberOffset
38where
39    H: BindUniform<Option<()>, f32, D>,
40    H: BindUniform<Option<()>, u32, D>,
41    H: BindUniform<Option<()>, i32, D>,
42    H: for<'a> BindUniform<Option<()>, &'a [f32; 3], D>,
43    H: for<'a> BindUniform<Option<()>, &'a [f32; 4], D>,
44    H: for<'a> BindUniform<Option<()>, &'a [f32; 16], D>,
45{
46    fn offset(&self) -> MemberOffset {
47        *self
48    }
49
50    fn context(&self) -> Option<()> {
51        None
52    }
53}
54
55/// Inputs to binding semantics
56pub struct UniformInputs<'a> {
57    /// MVP
58    pub mvp: &'a [f32; 16],
59    /// FrameCount
60    pub frame_count: u32,
61    /// Rotation
62    pub rotation: u32,
63    /// TotalSubFrames
64    pub total_subframes: u32,
65    /// CurrentSubFrame
66    pub current_subframe: u32,
67    /// FrameDirection
68    pub frame_direction: i32,
69    /// OriginalAspectRatio (need to normalize)
70    pub aspect_ratio: f32,
71    /// OriginalFPS
72    pub frames_per_second: f32,
73    /// FrameTimeDelta
74    pub frametime_delta: u32,
75    /// OutputSize
76    pub framebuffer_size: Size<u32>,
77    /// FinalViewportSize
78    pub viewport_size: Size<u32>,
79    /// Grouped HDR inputs.
80    pub hdr_inputs: HdrUniformInputs,
81    /// Grouped sensor (gyroscope/accelerometer) inputs.
82    pub sensor_inputs: SensorUniformInputs,
83}
84
85/// Three-axis sensor readings bound to the shader as `vec3` uniforms.
86pub struct SensorUniformInputs {
87    /// Gyroscope (x, y, z)
88    pub gyroscope: [f32; 3],
89    /// Accelerometer (x, y, z)
90    pub accelerometer: [f32; 3],
91    /// AccelerometerRest (Device position at launch / resume) (x, y, z)
92    pub accelerometer_rest: [f32; 3],
93}
94
95pub struct HdrUniformInputs {
96    /// HDRMode
97    pub color_space: librashader_common::ColorSpace,
98    /// BrightnessNits
99    pub brightness_nits: f32,
100    /// ExpandGamut
101    pub expand_gamut: u32,
102}
103
104/// Trait that abstracts binding of semantics to shader uniforms.
105pub trait BindSemantics<H = NoUniformBinder, C = Option<()>, U = Box<[u8]>, P = Box<[u8]>>
106where
107    C: Copy,
108    U: Deref<Target = [u8]> + DerefMut,
109    P: Deref<Target = [u8]> + DerefMut,
110    H: BindUniform<C, f32, Self::DeviceContext>,
111    H: BindUniform<C, u32, Self::DeviceContext>,
112    H: BindUniform<C, i32, Self::DeviceContext>,
113    H: for<'b> BindUniform<C, &'b [f32; 3], Self::DeviceContext>,
114    H: for<'b> BindUniform<C, &'b [f32; 4], Self::DeviceContext>,
115    H: for<'b> BindUniform<C, &'b [f32; 16], Self::DeviceContext>,
116{
117    /// The type of the input texture used for semantic binding.
118    type InputTexture: TextureInput;
119
120    /// The set of texture samplers available.
121    type SamplerSet;
122
123    /// The descriptor set or object that holds sampler and texture bindings.
124    type DescriptorSet<'a>;
125
126    /// The device context containing the state of the graphics processor.
127    type DeviceContext;
128
129    /// The type of uniform offsets to use.
130    type UniformOffset: ContextOffset<H, C, Self::DeviceContext>;
131
132    /// Bind a texture to the input descriptor set
133    fn bind_texture<'a>(
134        descriptors: &mut Self::DescriptorSet<'a>,
135        samplers: &Self::SamplerSet,
136        binding: &TextureBinding,
137        texture: &Self::InputTexture,
138        device: &Self::DeviceContext,
139    );
140
141    #[allow(clippy::too_many_arguments)]
142    /// Write uniform and texture semantics to the provided storages.
143    fn bind_semantics<'a>(
144        device: &Self::DeviceContext,
145        sampler_set: &Self::SamplerSet,
146        uniform_storage: &mut UniformStorage<H, C, U, P, Self::DeviceContext>,
147        descriptor_set: &mut Self::DescriptorSet<'a>,
148        uniform_inputs: UniformInputs<'_>,
149        original: &Self::InputTexture,
150        source: &Self::InputTexture,
151        uniform_bindings: &FastHashMap<UniformBinding, Self::UniformOffset>,
152        texture_meta: &FastHashMap<Semantic<TextureSemantics>, TextureBinding>,
153        pass_outputs: impl Iterator<Item = Option<impl AsRef<Self::InputTexture>>>,
154        pass_feedback: impl Iterator<Item = Option<impl AsRef<Self::InputTexture>>>,
155        original_history: impl Iterator<Item = Option<impl AsRef<Self::InputTexture>>>,
156        lookup_textures: impl Iterator<Item = (usize, impl AsRef<Self::InputTexture>)>,
157        parameter_defaults: &FastHashMap<ShortString, ShaderParameter>,
158        runtime_parameters: &RuntimeParameters,
159    ) {
160        let runtime_parameters = runtime_parameters.parameters.load();
161        // Bind MVP
162        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::MVP.into()) {
163            uniform_storage.bind_mat4(
164                offset.offset(),
165                uniform_inputs.mvp,
166                offset.context(),
167                device,
168            );
169        }
170
171        // Bind OutputSize
172        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::Output.into()) {
173            uniform_storage.bind_vec4(
174                offset.offset(),
175                uniform_inputs.framebuffer_size,
176                offset.context(),
177                device,
178            );
179        }
180
181        // bind FinalViewportSize
182        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::FinalViewport.into()) {
183            uniform_storage.bind_vec4(
184                offset.offset(),
185                uniform_inputs.viewport_size,
186                offset.context(),
187                device,
188            );
189        }
190
191        // bind FrameCount
192        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::FrameCount.into()) {
193            uniform_storage.bind_scalar(
194                offset.offset(),
195                uniform_inputs.frame_count,
196                offset.context(),
197                device,
198            );
199        }
200
201        // bind FrameDirection
202        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::FrameDirection.into()) {
203            uniform_storage.bind_scalar(
204                offset.offset(),
205                uniform_inputs.frame_direction,
206                offset.context(),
207                device,
208            );
209        }
210
211        // bind Rotation
212        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::Rotation.into()) {
213            uniform_storage.bind_scalar(
214                offset.offset(),
215                uniform_inputs.rotation,
216                offset.context(),
217                device,
218            );
219        }
220
221        // bind TotalSubFrames
222        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::TotalSubFrames.into()) {
223            uniform_storage.bind_scalar(
224                offset.offset(),
225                uniform_inputs.total_subframes,
226                offset.context(),
227                device,
228            );
229        }
230
231        // bind CurrentSubFrames
232        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::CurrentSubFrame.into()) {
233            uniform_storage.bind_scalar(
234                offset.offset(),
235                uniform_inputs.current_subframe,
236                offset.context(),
237                device,
238            );
239        }
240
241        // bind OriginalFPS
242        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::OriginalFPS.into()) {
243            uniform_storage.bind_scalar(
244                offset.offset(),
245                uniform_inputs.frames_per_second,
246                offset.context(),
247                device,
248            );
249        }
250
251        // bind FrameTimeDelta
252        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::FrameTimeDelta.into()) {
253            uniform_storage.bind_scalar(
254                offset.offset(),
255                uniform_inputs.frametime_delta,
256                offset.context(),
257                device,
258            );
259        }
260
261        let mut aspect_ratio = uniform_inputs.aspect_ratio;
262        if aspect_ratio.is_zero() {
263            aspect_ratio = original.size().aspect_ratio();
264        }
265
266        // bind OriginalAspect
267        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::OriginalAspect.into()) {
268            uniform_storage.bind_scalar(offset.offset(), aspect_ratio, offset.context(), device);
269        }
270
271        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::OriginalAspectRotated.into()) {
272            let rotated_aspect = if uniform_inputs.rotation == 1 || uniform_inputs.rotation == 3 {
273                1.0f32 / aspect_ratio
274            } else {
275                aspect_ratio
276            };
277
278            uniform_storage.bind_scalar(offset.offset(), rotated_aspect, offset.context(), device);
279        }
280
281        let hdr = uniform_inputs.hdr_inputs;
282        // bind HDRMode
283        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::HDRMode.into()) {
284            uniform_storage.bind_scalar(
285                offset.offset(),
286                hdr.color_space as u32,
287                offset.context(),
288                device,
289            );
290        }
291
292        // bind BrightnessNits
293        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::BrightnessNits.into()) {
294            uniform_storage.bind_scalar(
295                offset.offset(),
296                hdr.brightness_nits,
297                offset.context(),
298                device,
299            );
300        }
301
302        // bind ExpandGamut
303        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::ExpandGamut.into()) {
304            uniform_storage.bind_scalar(
305                offset.offset(),
306                hdr.expand_gamut,
307                offset.context(),
308                device,
309            );
310        }
311
312        // Scanlines, InverseTonemap, HDR10, and SubpixelLayout are RA-internal
313        // hdr.frag uniforms that librashader does not expose for user
314        // configuration (RA drives them from its own swapchain mastering
315        // pipeline). Bind 0 whenever a shader declares them so the slot is
316        // deterministic.
317        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::Scanlines.into()) {
318            uniform_storage.bind_scalar(offset.offset(), 0.0f32, offset.context(), device);
319        }
320        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::InverseTonemap.into()) {
321            uniform_storage.bind_scalar(offset.offset(), 0.0f32, offset.context(), device);
322        }
323        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::HDR10.into()) {
324            uniform_storage.bind_scalar(offset.offset(), 0.0f32, offset.context(), device);
325        }
326        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::SubpixelLayout.into()) {
327            uniform_storage.bind_scalar(offset.offset(), 0u32, offset.context(), device);
328        }
329
330        let sensors = uniform_inputs.sensor_inputs;
331        // bind Gyroscope
332        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::Gyroscope.into()) {
333            uniform_storage.bind_vec3(offset.offset(), sensors.gyroscope, offset.context(), device);
334        }
335
336        // bind Accelerometer
337        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::Accelerometer.into()) {
338            uniform_storage.bind_vec3(
339                offset.offset(),
340                sensors.accelerometer,
341                offset.context(),
342                device,
343            );
344        }
345
346        // bind AccelerometerRest
347        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::AccelerometerRest.into()) {
348            uniform_storage.bind_vec3(
349                offset.offset(),
350                sensors.accelerometer_rest,
351                offset.context(),
352                device,
353            );
354        }
355
356        // bind Original sampler
357        if let Some(binding) = texture_meta.get(&TextureSemantics::Original.semantics(0)) {
358            Self::bind_texture(descriptor_set, sampler_set, binding, original, device);
359        }
360
361        // bind OriginalSize
362        if let Some(offset) = uniform_bindings.get(&TextureSemantics::Original.semantics(0).into())
363        {
364            uniform_storage.bind_vec4(offset.offset(), original.size(), offset.context(), device);
365        }
366
367        // bind Source sampler
368        if let Some(binding) = texture_meta.get(&TextureSemantics::Source.semantics(0)) {
369            Self::bind_texture(descriptor_set, sampler_set, binding, source, device);
370        }
371
372        // bind SourceSize
373        if let Some(offset) = uniform_bindings.get(&TextureSemantics::Source.semantics(0).into()) {
374            uniform_storage.bind_vec4(offset.offset(), source.size(), offset.context(), device);
375        }
376
377        // OriginalHistory0 aliases OriginalHistory
378
379        // bind OriginalHistory0 sampler
380        if let Some(binding) = texture_meta.get(&TextureSemantics::OriginalHistory.semantics(0)) {
381            Self::bind_texture(descriptor_set, sampler_set, binding, original, device);
382        }
383
384        // bind OriginalHistory0Size
385        if let Some(offset) =
386            uniform_bindings.get(&TextureSemantics::OriginalHistory.semantics(0).into())
387        {
388            uniform_storage.bind_vec4(offset.offset(), original.size(), offset.context(), device);
389        }
390
391        // bind OriginalHistory1-..
392        for (index, history) in original_history.enumerate() {
393            let Some(history) = history else {
394                continue;
395            };
396
397            let history = history.as_ref();
398
399            if let Some(binding) =
400                texture_meta.get(&TextureSemantics::OriginalHistory.semantics(index + 1))
401            {
402                Self::bind_texture(descriptor_set, sampler_set, binding, history, device);
403            }
404
405            if let Some(offset) = uniform_bindings.get(
406                &TextureSemantics::OriginalHistory
407                    .semantics(index + 1)
408                    .into(),
409            ) {
410                uniform_storage.bind_vec4(
411                    offset.offset(),
412                    history.size(),
413                    offset.context(),
414                    device,
415                );
416            }
417        }
418
419        // bind PassOutput0..
420        // The caller should be responsible for limiting this up to
421        // pass_index
422        for (index, output) in pass_outputs.enumerate() {
423            let Some(output) = output else {
424                continue;
425            };
426
427            let output = output.as_ref();
428
429            if let Some(binding) = texture_meta.get(&TextureSemantics::PassOutput.semantics(index))
430            {
431                Self::bind_texture(descriptor_set, sampler_set, binding, output, device);
432            }
433
434            if let Some(offset) =
435                uniform_bindings.get(&TextureSemantics::PassOutput.semantics(index).into())
436            {
437                uniform_storage.bind_vec4(offset.offset(), output.size(), offset.context(), device);
438            }
439        }
440
441        // bind PassFeedback0..
442        for (index, feedback) in pass_feedback.enumerate() {
443            let Some(output) = feedback else {
444                continue;
445            };
446
447            let feedback = output.as_ref();
448
449            if let Some(binding) =
450                texture_meta.get(&TextureSemantics::PassFeedback.semantics(index))
451            {
452                Self::bind_texture(descriptor_set, sampler_set, binding, feedback, device);
453            }
454
455            if let Some(offset) =
456                uniform_bindings.get(&TextureSemantics::PassFeedback.semantics(index).into())
457            {
458                uniform_storage.bind_vec4(
459                    offset.offset(),
460                    feedback.size(),
461                    offset.context(),
462                    device,
463                );
464            }
465        }
466
467        // bind User parameters
468        for (id, offset) in uniform_bindings
469            .iter()
470            .filter_map(|(binding, value)| match binding {
471                UniformBinding::Parameter(id) => Some((id, value)),
472                _ => None,
473            })
474        {
475            let default = parameter_defaults.get(id).map_or(0f32, |f| f.initial);
476
477            let value = *runtime_parameters.get(id).unwrap_or(&default);
478
479            uniform_storage.bind_scalar(offset.offset(), value, offset.context(), device);
480        }
481
482        // bind luts
483        for (index, lut) in lookup_textures {
484            let lut = lut.as_ref();
485            if let Some(binding) = texture_meta.get(&TextureSemantics::User.semantics(index)) {
486                Self::bind_texture(descriptor_set, sampler_set, binding, lut, device);
487            }
488
489            if let Some(offset) =
490                uniform_bindings.get(&TextureSemantics::User.semantics(index).into())
491            {
492                uniform_storage.bind_vec4(offset.offset(), lut.size(), offset.context(), device);
493            }
494        }
495    }
496}
497
498#[derive(Debug)]
499pub struct BindingRequirements {
500    /// The number of `OriginalHistory` frames that must be kept.
501    pub(crate) required_history: usize,
502    /// Whether the final pass's output must be retained for feedback.
503    pub(crate) uses_final_pass_as_feedback: bool,
504    /// Pass indices whose output is referenced as `PassFeedback`.
505    pub(crate) feedback_mask: BitSet,
506    /// For each pass, the index of the last pass that reads its output this frame.
507    pub(crate) last_use: Box<[usize]>,
508}
509
510/// Trait for objects that can be used to create a binding map.
511pub trait BindingUtil {
512    /// Create the uniform binding map with the given reflection information.
513    fn create_binding_map<T>(
514        &self,
515        f: impl Fn(&dyn UniformMeta) -> T,
516    ) -> FastHashMap<UniformBinding, T>;
517
518    /// Calculate the number of required images for history.
519    fn calculate_requirements<'a>(pass_meta: impl Iterator<Item = &'a Self>) -> BindingRequirements
520    where
521        Self: 'a;
522}
523
524impl BindingUtil for BindingMeta {
525    fn create_binding_map<T>(
526        &self,
527        f: impl Fn(&dyn UniformMeta) -> T,
528    ) -> FastHashMap<UniformBinding, T> {
529        let mut uniform_bindings = FastHashMap::default();
530        for param in self.parameter_meta.values() {
531            uniform_bindings.insert(UniformBinding::Parameter(param.id.clone()), f(param));
532        }
533
534        for (semantics, param) in &self.unique_meta {
535            uniform_bindings.insert(UniformBinding::SemanticVariable(*semantics), f(param));
536        }
537
538        for (semantics, param) in &self.texture_size_meta {
539            uniform_bindings.insert(UniformBinding::TextureSize(*semantics), f(param));
540        }
541
542        uniform_bindings
543    }
544
545    fn calculate_requirements<'a>(pass_meta: impl Iterator<Item = &'a Self>) -> BindingRequirements
546    where
547        Self: 'a,
548    {
549        let passes: Vec<&BindingMeta> = pass_meta.collect();
550        let len = passes.len();
551
552        let mut required_images = 0;
553        let mut latest_feedback_pass: i64 = -1;
554        let mut feedback_mask = BitSet::new();
555
556        // Each pass output is read at least by its immediate successor (as `Source`), so its
557        // liveness extends to `index + 1`. The final pass has no successor.
558        let mut last_use = vec![0usize; len];
559        for index in 0..len {
560            last_use[index] = if index + 1 < len { index + 1 } else { index };
561        }
562
563        for (pass_index, pass) in passes.iter().enumerate() {
564            // If a shader uses history size, but not history, we still need to keep the texture.
565            let history_texture_max_index = pass
566                .texture_meta
567                .keys()
568                .filter(|semantics| semantics.semantics == TextureSemantics::OriginalHistory)
569                .map(|semantic| semantic.index)
570                .fold(0, std::cmp::max);
571            let history_texture_size_max_index = pass
572                .texture_size_meta
573                .keys()
574                .filter(|semantics| semantics.semantics == TextureSemantics::OriginalHistory)
575                .map(|semantic| semantic.index)
576                .fold(0, std::cmp::max);
577
578            // A pass output referenced by a future pass as PassFeedback needs its
579            // previous-frame output preserved.
580            for semantic in pass
581                .texture_meta
582                .keys()
583                .chain(pass.texture_size_meta.keys())
584                .filter(|semantics| semantics.semantics == TextureSemantics::PassFeedback)
585            {
586                feedback_mask.insert(semantic.index);
587                latest_feedback_pass = std::cmp::max(latest_feedback_pass, semantic.index as i64);
588            }
589
590            // A pass output sampled or sized as PassOutput is live until this consumer.
591            for semantic in pass
592                .texture_meta
593                .keys()
594                .chain(pass.texture_size_meta.keys())
595                .filter(|semantics| semantics.semantics == TextureSemantics::PassOutput)
596            {
597                if semantic.index < len {
598                    last_use[semantic.index] = std::cmp::max(last_use[semantic.index], pass_index);
599                }
600            }
601
602            required_images = std::cmp::max(required_images, history_texture_max_index);
603            required_images = std::cmp::max(required_images, history_texture_size_max_index);
604        }
605
606        // Feedback passes must survive the whole frame — their output is swapped into the
607        // feedback buffer at frame end — so pin their liveness to the final pass.
608        for index in feedback_mask.iter() {
609            if index < len {
610                last_use[index] = len.saturating_sub(1);
611            }
612        }
613
614        let uses_feedback = if latest_feedback_pass.is_negative() {
615            false
616        } else {
617            // Technically = but we can be permissive here
618
619            // account for off by 1
620            latest_feedback_pass + 1 >= len as i64
621        };
622
623        BindingRequirements {
624            required_history: required_images,
625            uses_final_pass_as_feedback: uses_feedback,
626            feedback_mask,
627            last_use: last_use.into_boxed_slice(),
628        }
629    }
630}
631
632#[macro_export]
633macro_rules! impl_default_frame_options {
634    ($ty:ident) => {
635        /// Options for each frame.
636        #[repr(C)]
637        #[derive(Debug, Clone)]
638        pub struct $ty {
639            /// Whether or not to clear the history buffers.
640            pub clear_history: bool,
641            /// The direction of rendering.
642            /// -1 indicates that the frames are played in reverse order.
643            pub frame_direction: i32,
644            /// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
645            pub rotation: u32,
646            /// The total number of subframes ran. Default is 1.
647            pub total_subframes: u32,
648            /// The current sub frame. Default is 1.
649            pub current_subframe: u32,
650            /// The expected aspect ratio of the source image.
651            ///
652            /// This can differ from the actual aspect ratio of the source
653            /// image.
654            ///
655            /// The default is 0 which will automatically infer the ratio from the source image.
656            pub aspect_ratio: f32,
657            /// The original frames per second of the source. Default is 1.
658            pub frames_per_second: f32,
659            /// Time in milliseconds between the current and previous frame. Default is 0.
660            pub frametime_delta: u32,
661            /// Target color space bound to the shader `HDRMode` uniform. Must
662            /// match the host swapchain color space. Default is
663            /// `ColorSpace::Sdr`.
664            pub color_space: $crate::__ColorSpace,
665            /// HDR SDR reference white in nits, bound to the shader `BrightnessNits` uniform.
666            /// Default is 200.0. Only meaningful when the chain's HDR mode is non-zero.
667            pub brightness_nits: f32,
668            /// Gamut expansion mode bound to the shader `ExpandGamut` uniform. Default is 0.
669            pub expand_gamut: u32,
670            /// Bound to the shader `Gyroscope` (vec3) uniform. Default is [0, 0, 0].
671            pub gyroscope: [f32; 3],
672            /// Bound to the shader `Accelerometer` (vec3) uniform. Default is [0, 0, 0].
673            pub accelerometer: [f32; 3],
674            /// Bound to the shader `AccelerometerRest` (vec3) uniform. Default is [0, 0, 0].
675            pub accelerometer_rest: [f32; 3],
676        }
677
678        impl Default for $ty {
679            fn default() -> Self {
680                Self {
681                    clear_history: false,
682                    frame_direction: 1,
683                    rotation: 0,
684                    total_subframes: 1,
685                    current_subframe: 1,
686                    aspect_ratio: 0.0,
687                    frametime_delta: 0,
688                    frames_per_second: 1.0,
689                    color_space: $crate::__ColorSpace::Sdr,
690                    brightness_nits: 200.0,
691                    expand_gamut: 0,
692                    gyroscope: [0.0, 0.0, 0.0],
693                    accelerometer: [0.0, 0.0, 0.0],
694                    accelerometer_rest: [0.0, 0.0, 0.0],
695                }
696            }
697        }
698    };
699}