librashader_runtime/
binding.rs

1use crate::parameters::RuntimeParameters;
2use crate::uniforms::{BindUniform, NoUniformBinder, UniformStorage};
3use librashader_common::map::{FastHashMap, ShortString};
4use librashader_common::Size;
5use librashader_preprocess::ShaderParameter;
6use librashader_reflect::reflect::semantics::{
7    BindingMeta, MemberOffset, Semantic, TextureBinding, TextureSemantics, UniformBinding,
8    UniformMeta, UniqueSemantics,
9};
10use num_traits::Zero;
11use std::ops::{Deref, DerefMut};
12
13/// Trait for input textures used during uniform binding,
14pub trait TextureInput {
15    /// Gets the size of this input texture.
16    fn size(&self) -> Size<u32>;
17}
18
19/// A uniform member offset with context that needs to be resolved.
20pub trait ContextOffset<H, C, D = ()>
21where
22    H: BindUniform<C, f32, D>,
23    H: BindUniform<C, u32, D>,
24    H: BindUniform<C, i32, D>,
25    H: for<'a> BindUniform<C, &'a [f32; 4], D>,
26    H: for<'a> BindUniform<C, &'a [f32; 16], D>,
27{
28    /// Gets the `MemberOffset` part of the offset.
29    fn offset(&self) -> MemberOffset;
30
31    /// Gets the context part of the offset.
32    fn context(&self) -> C;
33}
34
35impl<D, H> ContextOffset<H, Option<()>, D> for MemberOffset
36where
37    H: BindUniform<Option<()>, f32, D>,
38    H: BindUniform<Option<()>, u32, D>,
39    H: BindUniform<Option<()>, i32, D>,
40    H: for<'a> BindUniform<Option<()>, &'a [f32; 4], D>,
41    H: for<'a> BindUniform<Option<()>, &'a [f32; 16], D>,
42{
43    fn offset(&self) -> MemberOffset {
44        *self
45    }
46
47    fn context(&self) -> Option<()> {
48        None
49    }
50}
51
52/// Inputs to binding semantics
53pub struct UniformInputs<'a> {
54    /// MVP
55    pub mvp: &'a [f32; 16],
56    /// FrameCount
57    pub frame_count: u32,
58    /// Rotation
59    pub rotation: u32,
60    /// TotalSubFrames
61    pub total_subframes: u32,
62    /// CurrentSubFrame
63    pub current_subframe: u32,
64    /// FrameDirection
65    pub frame_direction: i32,
66    /// OriginalAspectRatio (need to normalize)
67    pub aspect_ratio: f32,
68    /// OriginalFPS
69    pub frames_per_second: f32,
70    /// FrameTimeDelta
71    pub frametime_delta: u32,
72    /// OutputSize
73    pub framebuffer_size: Size<u32>,
74    /// FinalViewportSize
75    pub viewport_size: Size<u32>,
76}
77
78/// Trait that abstracts binding of semantics to shader uniforms.
79pub trait BindSemantics<H = NoUniformBinder, C = Option<()>, U = Box<[u8]>, P = Box<[u8]>>
80where
81    C: Copy,
82    U: Deref<Target = [u8]> + DerefMut,
83    P: Deref<Target = [u8]> + DerefMut,
84    H: BindUniform<C, f32, Self::DeviceContext>,
85    H: BindUniform<C, u32, Self::DeviceContext>,
86    H: BindUniform<C, i32, Self::DeviceContext>,
87    H: for<'b> BindUniform<C, &'b [f32; 4], Self::DeviceContext>,
88    H: for<'b> BindUniform<C, &'b [f32; 16], Self::DeviceContext>,
89{
90    /// The type of the input texture used for semantic binding.
91    type InputTexture: TextureInput;
92
93    /// The set of texture samplers available.
94    type SamplerSet;
95
96    /// The descriptor set or object that holds sampler and texture bindings.
97    type DescriptorSet<'a>;
98
99    /// The device context containing the state of the graphics processor.
100    type DeviceContext;
101
102    /// The type of uniform offsets to use.
103    type UniformOffset: ContextOffset<H, C, Self::DeviceContext>;
104
105    /// Bind a texture to the input descriptor set
106    fn bind_texture<'a>(
107        descriptors: &mut Self::DescriptorSet<'a>,
108        samplers: &Self::SamplerSet,
109        binding: &TextureBinding,
110        texture: &Self::InputTexture,
111        device: &Self::DeviceContext,
112    );
113
114    #[allow(clippy::too_many_arguments)]
115    /// Write uniform and texture semantics to the provided storages.
116    fn bind_semantics<'a>(
117        device: &Self::DeviceContext,
118        sampler_set: &Self::SamplerSet,
119        uniform_storage: &mut UniformStorage<H, C, U, P, Self::DeviceContext>,
120        descriptor_set: &mut Self::DescriptorSet<'a>,
121        uniform_inputs: UniformInputs<'_>,
122        original: &Self::InputTexture,
123        source: &Self::InputTexture,
124        uniform_bindings: &FastHashMap<UniformBinding, Self::UniformOffset>,
125        texture_meta: &FastHashMap<Semantic<TextureSemantics>, TextureBinding>,
126        pass_outputs: impl Iterator<Item = Option<impl AsRef<Self::InputTexture>>>,
127        pass_feedback: impl Iterator<Item = Option<impl AsRef<Self::InputTexture>>>,
128        original_history: impl Iterator<Item = Option<impl AsRef<Self::InputTexture>>>,
129        lookup_textures: impl Iterator<Item = (usize, impl AsRef<Self::InputTexture>)>,
130        parameter_defaults: &FastHashMap<ShortString, ShaderParameter>,
131        runtime_parameters: &RuntimeParameters,
132    ) {
133        let runtime_parameters = runtime_parameters.parameters.load();
134        // Bind MVP
135        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::MVP.into()) {
136            uniform_storage.bind_mat4(
137                offset.offset(),
138                uniform_inputs.mvp,
139                offset.context(),
140                device,
141            );
142        }
143
144        // Bind OutputSize
145        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::Output.into()) {
146            uniform_storage.bind_vec4(
147                offset.offset(),
148                uniform_inputs.framebuffer_size,
149                offset.context(),
150                device,
151            );
152        }
153
154        // bind FinalViewportSize
155        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::FinalViewport.into()) {
156            uniform_storage.bind_vec4(
157                offset.offset(),
158                uniform_inputs.viewport_size,
159                offset.context(),
160                device,
161            );
162        }
163
164        // bind FrameCount
165        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::FrameCount.into()) {
166            uniform_storage.bind_scalar(
167                offset.offset(),
168                uniform_inputs.frame_count,
169                offset.context(),
170                device,
171            );
172        }
173
174        // bind FrameDirection
175        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::FrameDirection.into()) {
176            uniform_storage.bind_scalar(
177                offset.offset(),
178                uniform_inputs.frame_direction,
179                offset.context(),
180                device,
181            );
182        }
183
184        // bind Rotation
185        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::Rotation.into()) {
186            uniform_storage.bind_scalar(
187                offset.offset(),
188                uniform_inputs.rotation,
189                offset.context(),
190                device,
191            );
192        }
193
194        // bind TotalSubFrames
195        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::TotalSubFrames.into()) {
196            uniform_storage.bind_scalar(
197                offset.offset(),
198                uniform_inputs.total_subframes,
199                offset.context(),
200                device,
201            );
202        }
203
204        // bind CurrentSubFrames
205        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::CurrentSubFrame.into()) {
206            uniform_storage.bind_scalar(
207                offset.offset(),
208                uniform_inputs.current_subframe,
209                offset.context(),
210                device,
211            );
212        }
213
214        // bind OriginalFPS
215        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::OriginalFPS.into()) {
216            uniform_storage.bind_scalar(
217                offset.offset(),
218                uniform_inputs.frames_per_second,
219                offset.context(),
220                device,
221            );
222        }
223
224        // bind FrameTimeDelta
225        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::FrameTimeDelta.into()) {
226            uniform_storage.bind_scalar(
227                offset.offset(),
228                uniform_inputs.frametime_delta,
229                offset.context(),
230                device,
231            );
232        }
233
234        let mut aspect_ratio = uniform_inputs.aspect_ratio;
235        if aspect_ratio.is_zero() {
236            aspect_ratio = original.size().aspect_ratio();
237        }
238
239        // bind OriginalAspect
240        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::OriginalAspect.into()) {
241            uniform_storage.bind_scalar(offset.offset(), aspect_ratio, offset.context(), device);
242        }
243
244        if let Some(offset) = uniform_bindings.get(&UniqueSemantics::OriginalAspectRotated.into()) {
245            let rotated_aspect = if uniform_inputs.rotation == 1 || uniform_inputs.rotation == 3 {
246                1.0f32 / aspect_ratio
247            } else {
248                aspect_ratio
249            };
250
251            uniform_storage.bind_scalar(offset.offset(), rotated_aspect, offset.context(), device);
252        }
253
254        // bind Original sampler
255        if let Some(binding) = texture_meta.get(&TextureSemantics::Original.semantics(0)) {
256            Self::bind_texture(descriptor_set, sampler_set, binding, original, device);
257        }
258
259        // bind OriginalSize
260        if let Some(offset) = uniform_bindings.get(&TextureSemantics::Original.semantics(0).into())
261        {
262            uniform_storage.bind_vec4(offset.offset(), original.size(), offset.context(), device);
263        }
264
265        // bind Source sampler
266        if let Some(binding) = texture_meta.get(&TextureSemantics::Source.semantics(0)) {
267            Self::bind_texture(descriptor_set, sampler_set, binding, source, device);
268        }
269
270        // bind SourceSize
271        if let Some(offset) = uniform_bindings.get(&TextureSemantics::Source.semantics(0).into()) {
272            uniform_storage.bind_vec4(offset.offset(), source.size(), offset.context(), device);
273        }
274
275        // OriginalHistory0 aliases OriginalHistory
276
277        // bind OriginalHistory0 sampler
278        if let Some(binding) = texture_meta.get(&TextureSemantics::OriginalHistory.semantics(0)) {
279            Self::bind_texture(descriptor_set, sampler_set, binding, original, device);
280        }
281
282        // bind OriginalHistory0Size
283        if let Some(offset) =
284            uniform_bindings.get(&TextureSemantics::OriginalHistory.semantics(0).into())
285        {
286            uniform_storage.bind_vec4(offset.offset(), original.size(), offset.context(), device);
287        }
288
289        // bind OriginalHistory1-..
290        for (index, history) in original_history.enumerate() {
291            let Some(history) = history else {
292                continue;
293            };
294
295            let history = history.as_ref();
296
297            if let Some(binding) =
298                texture_meta.get(&TextureSemantics::OriginalHistory.semantics(index + 1))
299            {
300                Self::bind_texture(descriptor_set, sampler_set, binding, history, device);
301            }
302
303            if let Some(offset) = uniform_bindings.get(
304                &TextureSemantics::OriginalHistory
305                    .semantics(index + 1)
306                    .into(),
307            ) {
308                uniform_storage.bind_vec4(
309                    offset.offset(),
310                    history.size(),
311                    offset.context(),
312                    device,
313                );
314            }
315        }
316
317        // bind PassOutput0..
318        // The caller should be responsible for limiting this up to
319        // pass_index
320        for (index, output) in pass_outputs.enumerate() {
321            let Some(output) = output else {
322                continue;
323            };
324
325            let output = output.as_ref();
326
327            if let Some(binding) = texture_meta.get(&TextureSemantics::PassOutput.semantics(index))
328            {
329                Self::bind_texture(descriptor_set, sampler_set, binding, output, device);
330            }
331
332            if let Some(offset) =
333                uniform_bindings.get(&TextureSemantics::PassOutput.semantics(index).into())
334            {
335                uniform_storage.bind_vec4(offset.offset(), output.size(), offset.context(), device);
336            }
337        }
338
339        // bind PassFeedback0..
340        for (index, feedback) in pass_feedback.enumerate() {
341            let Some(output) = feedback else {
342                continue;
343            };
344
345            let feedback = output.as_ref();
346
347            if let Some(binding) =
348                texture_meta.get(&TextureSemantics::PassFeedback.semantics(index))
349            {
350                Self::bind_texture(descriptor_set, sampler_set, binding, feedback, device);
351            }
352
353            if let Some(offset) =
354                uniform_bindings.get(&TextureSemantics::PassFeedback.semantics(index).into())
355            {
356                uniform_storage.bind_vec4(
357                    offset.offset(),
358                    feedback.size(),
359                    offset.context(),
360                    device,
361                );
362            }
363        }
364
365        // bind User parameters
366        for (id, offset) in uniform_bindings
367            .iter()
368            .filter_map(|(binding, value)| match binding {
369                UniformBinding::Parameter(id) => Some((id, value)),
370                _ => None,
371            })
372        {
373            let id = id.as_str();
374
375            let default = parameter_defaults.get(id).map_or(0f32, |f| f.initial);
376
377            let value = *runtime_parameters.get(id).unwrap_or(&default);
378
379            uniform_storage.bind_scalar(offset.offset(), value, offset.context(), device);
380        }
381
382        // bind luts
383        for (index, lut) in lookup_textures {
384            let lut = lut.as_ref();
385            if let Some(binding) = texture_meta.get(&TextureSemantics::User.semantics(index)) {
386                Self::bind_texture(descriptor_set, sampler_set, binding, lut, device);
387            }
388
389            if let Some(offset) =
390                uniform_bindings.get(&TextureSemantics::User.semantics(index).into())
391            {
392                uniform_storage.bind_vec4(offset.offset(), lut.size(), offset.context(), device);
393            }
394        }
395    }
396}
397
398#[derive(Debug)]
399pub struct BindingRequirements {
400    pub(crate) required_history: usize,
401    pub(crate) uses_final_pass_as_feedback: bool,
402}
403
404/// Trait for objects that can be used to create a binding map.
405pub trait BindingUtil {
406    /// Create the uniform binding map with the given reflection information.
407    fn create_binding_map<T>(
408        &self,
409        f: impl Fn(&dyn UniformMeta) -> T,
410    ) -> FastHashMap<UniformBinding, T>;
411
412    /// Calculate the number of required images for history.
413    fn calculate_requirements<'a>(pass_meta: impl Iterator<Item = &'a Self>) -> BindingRequirements
414    where
415        Self: 'a;
416}
417
418impl BindingUtil for BindingMeta {
419    fn create_binding_map<T>(
420        &self,
421        f: impl Fn(&dyn UniformMeta) -> T,
422    ) -> FastHashMap<UniformBinding, T> {
423        let mut uniform_bindings = FastHashMap::default();
424        for param in self.parameter_meta.values() {
425            uniform_bindings.insert(UniformBinding::Parameter(param.id.clone()), f(param));
426        }
427
428        for (semantics, param) in &self.unique_meta {
429            uniform_bindings.insert(UniformBinding::SemanticVariable(*semantics), f(param));
430        }
431
432        for (semantics, param) in &self.texture_size_meta {
433            uniform_bindings.insert(UniformBinding::TextureSize(*semantics), f(param));
434        }
435
436        uniform_bindings
437    }
438
439    fn calculate_requirements<'a>(pass_meta: impl Iterator<Item = &'a Self>) -> BindingRequirements
440    where
441        Self: 'a,
442    {
443        let mut required_images = 0;
444
445        let mut len: i64 = 0;
446        let mut latest_feedback_pass: i64 = -1;
447
448        for pass in pass_meta {
449            len += 1;
450
451            // If a shader uses history size, but not history, we still need to keep the texture.
452            let history_texture_max_index = pass
453                .texture_meta
454                .iter()
455                .filter(|(semantics, _)| semantics.semantics == TextureSemantics::OriginalHistory)
456                .map(|(semantic, _)| semantic.index)
457                .fold(0, std::cmp::max);
458            let history_texture_size_max_index = pass
459                .texture_size_meta
460                .iter()
461                .filter(|(semantics, _)| semantics.semantics == TextureSemantics::OriginalHistory)
462                .map(|(semantic, _)| semantic.index)
463                .fold(0, std::cmp::max);
464
465            let feedback_max_index = pass
466                .texture_meta
467                .iter()
468                .filter(|(semantics, _)| semantics.semantics == TextureSemantics::PassFeedback)
469                .map(|(semantic, _)| semantic.index as i64)
470                .fold(-1, std::cmp::max);
471            let feedback_max_size_index = pass
472                .texture_size_meta
473                .iter()
474                .filter(|(semantics, _)| semantics.semantics == TextureSemantics::PassFeedback)
475                .map(|(semantic, _)| semantic.index as i64)
476                .fold(-1, std::cmp::max);
477
478            latest_feedback_pass = std::cmp::max(latest_feedback_pass, feedback_max_index);
479            latest_feedback_pass = std::cmp::max(latest_feedback_pass, feedback_max_size_index);
480
481            required_images = std::cmp::max(required_images, history_texture_max_index);
482            required_images = std::cmp::max(required_images, history_texture_size_max_index);
483        }
484
485        let uses_feedback = if latest_feedback_pass.is_negative() {
486            false
487        } else {
488            // Technically = but we can be permissive here
489
490            // account for off by 1
491            latest_feedback_pass + 1 >= len
492        };
493
494        BindingRequirements {
495            required_history: required_images,
496            uses_final_pass_as_feedback: uses_feedback,
497        }
498    }
499}
500
501#[macro_export]
502macro_rules! impl_default_frame_options {
503    ($ty:ident) => {
504        /// Options for each frame.
505        #[repr(C)]
506        #[derive(Debug, Clone)]
507        pub struct $ty {
508            /// Whether or not to clear the history buffers.
509            pub clear_history: bool,
510            /// The direction of rendering.
511            /// -1 indicates that the frames are played in reverse order.
512            pub frame_direction: i32,
513            /// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg.
514            pub rotation: u32,
515            /// The total number of subframes ran. Default is 1.
516            pub total_subframes: u32,
517            /// The current sub frame. Default is 1.
518            pub current_subframe: u32,
519            /// The expected aspect ratio of the source image.
520            ///
521            /// This can differ from the actual aspect ratio of the source
522            /// image.
523            ///
524            /// The default is 0 which will automatically infer the ratio from the source image.
525            pub aspect_ratio: f32,
526            /// The original frames per second of the source. Default is 1.
527            pub frames_per_second: f32,
528            /// Time in milliseconds between the current and previous frame. Default is 0.
529            pub frametime_delta: u32,
530        }
531
532        impl Default for $ty {
533            fn default() -> Self {
534                Self {
535                    clear_history: false,
536                    frame_direction: 1,
537                    rotation: 0,
538                    total_subframes: 1,
539                    current_subframe: 1,
540                    aspect_ratio: 0.0,
541                    frametime_delta: 0,
542                    frames_per_second: 1.0,
543                }
544            }
545        }
546    };
547}