Skip to main content

fyrox_impl/renderer/cache/
shader.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use crate::{
22    core::{arrayvec::ArrayVec, log::Log, math::Rect, sstorage::ImmutableString},
23    graphics::{
24        error::FrameworkError,
25        framebuffer::{DrawCallStatistics, GpuFrameBuffer, ResourceBindGroup, ResourceBinding},
26        geometry_buffer::GpuGeometryBuffer,
27        gpu_program::{GpuProgram, ShaderResourceDefinition, ShaderResourceKind},
28        gpu_texture::GpuTexture,
29        sampler::GpuSampler,
30        server::GraphicsServer,
31        uniform::StaticUniformBuffer,
32        DrawParameters, ElementRange,
33    },
34    material::{
35        shader::{Shader, ShaderResource},
36        MaterialPropertyRef,
37    },
38    renderer::{
39        bundle,
40        cache::{uniform::UniformBufferCache, TemporaryCache},
41    },
42};
43use fxhash::FxHashMap;
44use std::ops::Deref;
45
46pub struct NamedValue<T> {
47    pub name: ImmutableString,
48    pub value: T,
49}
50
51impl<T> NamedValue<T> {
52    pub fn new(name: impl Into<ImmutableString>, value: T) -> Self {
53        Self {
54            name: name.into(),
55            value,
56        }
57    }
58}
59
60pub struct NamedValuesContainer<T, const N: usize> {
61    properties: [NamedValue<T>; N],
62}
63
64fn search<'a, T>(slice: &'a [NamedValue<T>], name: &ImmutableString) -> Option<&'a NamedValue<T>> {
65    slice
66        .binary_search_by(|prop| prop.name.cached_hash().cmp(&name.cached_hash()))
67        .ok()
68        .and_then(|idx| slice.get(idx))
69}
70
71impl<T, const N: usize> NamedValuesContainer<T, N> {
72    pub fn property_ref(&self, name: &ImmutableString) -> Option<&NamedValue<T>> {
73        search(&self.properties, name)
74    }
75
76    pub fn data_ref(&self) -> NamedValuesContainerRef<'_, T> {
77        NamedValuesContainerRef {
78            properties: &self.properties,
79        }
80    }
81}
82
83impl<T, const N: usize> From<[NamedValue<T>; N]> for NamedValuesContainer<T, N> {
84    fn from(mut value: [NamedValue<T>; N]) -> Self {
85        value.sort_unstable_by_key(|prop| prop.name.cached_hash());
86        Self { properties: value }
87    }
88}
89
90impl<T, const N: usize> Deref for NamedValuesContainer<T, N> {
91    type Target = [NamedValue<T>];
92
93    fn deref(&self) -> &Self::Target {
94        &self.properties
95    }
96}
97
98pub struct NamedValuesContainerRef<'a, T> {
99    properties: &'a [NamedValue<T>],
100}
101
102impl<T> NamedValuesContainerRef<'_, T> {
103    pub fn property_ref(&self, name: &ImmutableString) -> Option<&NamedValue<T>> {
104        search(self.properties, name)
105    }
106}
107
108pub struct PropertyGroup<'a, const N: usize> {
109    pub properties: NamedValuesContainer<MaterialPropertyRef<'a>, N>,
110}
111
112pub type NamedPropertyRef<'a> = NamedValue<MaterialPropertyRef<'a>>;
113
114pub fn property<'a>(
115    name: impl Into<ImmutableString>,
116    value: impl Into<MaterialPropertyRef<'a>>,
117) -> NamedPropertyRef<'a> {
118    NamedValue::new(name, value.into())
119}
120
121pub fn binding<'a, 'b>(
122    name: impl Into<ImmutableString>,
123    value: impl Into<GpuResourceBinding<'a, 'b>>,
124) -> NamedValue<GpuResourceBinding<'a, 'b>> {
125    NamedValue::new(name, value.into())
126}
127
128impl<'a> From<(&'a GpuTexture, &'a GpuSampler)> for GpuResourceBinding<'a, '_> {
129    fn from(value: (&'a GpuTexture, &'a GpuSampler)) -> Self {
130        GpuResourceBinding::Texture {
131            texture: value.0,
132            sampler: value.1,
133        }
134    }
135}
136
137impl<'a, 'b, const N: usize> From<&'a PropertyGroup<'b, N>> for GpuResourceBinding<'a, 'b> {
138    fn from(value: &'a PropertyGroup<'b, N>) -> Self {
139        GpuResourceBinding::PropertyGroup {
140            properties: value.properties.data_ref(),
141        }
142    }
143}
144
145impl<'a, const N: usize> From<[NamedPropertyRef<'a>; N]> for PropertyGroup<'a, N> {
146    fn from(value: [NamedPropertyRef<'a>; N]) -> Self {
147        Self {
148            properties: value.into(),
149        }
150    }
151}
152
153impl<'a, const N: usize> Deref for PropertyGroup<'a, N> {
154    type Target = [NamedValue<MaterialPropertyRef<'a>>];
155
156    fn deref(&self) -> &Self::Target {
157        &self.properties
158    }
159}
160
161pub enum GpuResourceBinding<'a, 'b> {
162    Texture {
163        texture: &'a GpuTexture,
164        sampler: &'a GpuSampler,
165    },
166    PropertyGroup {
167        properties: NamedValuesContainerRef<'a, MaterialPropertyRef<'b>>,
168    },
169}
170
171impl<'a, 'b> GpuResourceBinding<'a, 'b> {
172    pub fn texture(texture: &'a GpuTexture, sampler: &'a GpuSampler) -> Self {
173        Self::Texture { texture, sampler }
174    }
175
176    pub fn property_group<const N: usize>(properties: &'a PropertyGroup<'b, N>) -> Self {
177        Self::PropertyGroup {
178            properties: properties.properties.data_ref(),
179        }
180    }
181}
182
183pub struct RenderMaterial<'a, 'b, const N: usize> {
184    pub bindings: NamedValuesContainer<GpuResourceBinding<'a, 'b>, N>,
185}
186
187impl<'a, 'b, const N: usize> From<[NamedValue<GpuResourceBinding<'a, 'b>>; N]>
188    for RenderMaterial<'a, 'b, N>
189{
190    fn from(value: [NamedValue<GpuResourceBinding<'a, 'b>>; N]) -> Self {
191        Self {
192            bindings: value.into(),
193        }
194    }
195}
196
197pub struct RenderPassData {
198    pub program: GpuProgram,
199    pub draw_params: DrawParameters,
200}
201
202pub struct RenderPassContainer {
203    pub resources: Vec<ShaderResourceDefinition>,
204    pub render_passes: FxHashMap<ImmutableString, RenderPassData>,
205}
206
207impl RenderPassContainer {
208    pub fn from_str(server: &dyn GraphicsServer, str: &str) -> Result<Self, FrameworkError> {
209        let shader = Shader::from_string(str).map_err(|e| FrameworkError::Custom(e.to_string()))?;
210        Self::new(server, &shader)
211    }
212
213    pub fn new(server: &dyn GraphicsServer, shader: &Shader) -> Result<Self, FrameworkError> {
214        let mut render_passes = FxHashMap::default();
215
216        for render_pass in shader.definition.passes.iter() {
217            let program_name = format!("{}_{}", shader.definition.name, render_pass.name);
218            match server.create_program(
219                &program_name,
220                render_pass.vertex_shader.0.clone(),
221                render_pass.vertex_shader_line,
222                render_pass.fragment_shader.0.clone(),
223                render_pass.fragment_shader_line,
224                &shader.definition.resources,
225            ) {
226                Ok(gpu_program) => {
227                    render_passes.insert(
228                        ImmutableString::new(&render_pass.name),
229                        RenderPassData {
230                            program: gpu_program,
231                            draw_params: render_pass.draw_parameters.clone(),
232                        },
233                    );
234                }
235                Err(e) => {
236                    return Err(FrameworkError::Custom(format!(
237                        "Failed to create {program_name} shader' GPU program. Reason: {e}"
238                    )));
239                }
240            };
241        }
242
243        Ok(Self {
244            render_passes,
245            resources: shader.definition.resources.clone(),
246        })
247    }
248
249    pub fn get(
250        &self,
251        render_pass_name: &ImmutableString,
252    ) -> Result<&RenderPassData, FrameworkError> {
253        self.render_passes.get(render_pass_name).ok_or_else(|| {
254            FrameworkError::Custom(format!("No render pass with name {render_pass_name}!"))
255        })
256    }
257
258    pub fn run_pass<const N: usize>(
259        &self,
260        instance_count: usize,
261        render_pass_name: &ImmutableString,
262        framebuffer: &GpuFrameBuffer,
263        geometry: &GpuGeometryBuffer,
264        viewport: Rect<i32>,
265        material: &RenderMaterial<'_, '_, N>,
266        uniform_buffer_cache: &mut UniformBufferCache,
267        element_range: ElementRange,
268        override_params: Option<&DrawParameters>,
269    ) -> Result<DrawCallStatistics, FrameworkError> {
270        if instance_count == 0 {
271            return Ok(Default::default());
272        }
273
274        let render_pass = self.get(render_pass_name)?;
275
276        let mut resource_bindings = ArrayVec::<ResourceBinding, 32>::new();
277
278        for resource in self.resources.iter() {
279            // Ignore built-in groups.
280            if resource.is_built_in() {
281                continue;
282            }
283
284            match resource.kind {
285                ShaderResourceKind::Texture { .. } => {
286                    if let Some((tex, sampler)) = material
287                        .bindings
288                        .property_ref(&resource.name)
289                        .and_then(|p| {
290                            if let GpuResourceBinding::Texture { texture, sampler } = p.value {
291                                Some((texture, sampler))
292                            } else {
293                                None
294                            }
295                        })
296                    {
297                        resource_bindings.push(ResourceBinding::texture(
298                            tex,
299                            sampler,
300                            resource.binding,
301                        ));
302                    } else {
303                        return Err(FrameworkError::Custom(format!(
304                            "No texture bound to {} resource binding!",
305                            resource.name
306                        )));
307                    }
308                }
309                ShaderResourceKind::PropertyGroup(ref shader_property_group) => {
310                    let mut buf = StaticUniformBuffer::<16384>::new();
311
312                    if let Some(material_property_group) = material
313                        .bindings
314                        .property_ref(&resource.name)
315                        .and_then(|p| {
316                            if let GpuResourceBinding::PropertyGroup { ref properties } = p.value {
317                                Some(properties)
318                            } else {
319                                None
320                            }
321                        })
322                    {
323                        bundle::write_with_material(
324                            shader_property_group,
325                            material_property_group,
326                            |c: &NamedValuesContainerRef<MaterialPropertyRef>, n| {
327                                c.property_ref(n).map(|v| v.value)
328                            },
329                            &mut buf,
330                        );
331                    } else {
332                        // No respective resource bound in the material, use shader defaults. This is very
333                        // important, because some drivers will crash if uniform buffer has insufficient
334                        // data.
335                        bundle::write_shader_values(shader_property_group, &mut buf)
336                    }
337
338                    resource_bindings.push(ResourceBinding::buffer(
339                        &uniform_buffer_cache.write(buf)?,
340                        resource.binding,
341                        Default::default(),
342                    ));
343                }
344            }
345        }
346
347        let resources = [ResourceBindGroup {
348            bindings: &resource_bindings,
349        }];
350
351        if instance_count == 1 {
352            framebuffer.draw(
353                geometry,
354                viewport,
355                &render_pass.program,
356                override_params.unwrap_or(&render_pass.draw_params),
357                &resources,
358                element_range,
359            )
360        } else {
361            framebuffer.draw_instances(
362                instance_count,
363                geometry,
364                viewport,
365                &render_pass.program,
366                override_params.unwrap_or(&render_pass.draw_params),
367                &resources,
368                element_range,
369            )
370        }
371    }
372}
373
374#[derive(Default)]
375pub struct ShaderCache {
376    pub(super) cache: TemporaryCache<RenderPassContainer>,
377}
378
379impl ShaderCache {
380    pub fn remove(&mut self, shader: &ShaderResource) {
381        let mut state = shader.state();
382        if let Some(shader_state) = state.data() {
383            self.cache.remove(&shader_state.cache_index);
384        }
385    }
386
387    pub fn get(
388        &mut self,
389        server: &dyn GraphicsServer,
390        shader: &ShaderResource,
391    ) -> Option<&RenderPassContainer> {
392        let mut shader_state = shader.state();
393
394        if let Some(shader_state) = shader_state.data() {
395            match self.cache.get_or_insert_with(
396                &shader_state.cache_index,
397                Default::default(),
398                || RenderPassContainer::new(server, shader_state),
399            ) {
400                Ok(shader_set) => Some(shader_set),
401                Err(error) => {
402                    Log::err(format!("{error}"));
403                    None
404                }
405            }
406        } else {
407            None
408        }
409    }
410
411    pub fn update(&mut self, dt: f32) {
412        self.cache.update(dt)
413    }
414
415    pub fn clear(&mut self) {
416        self.cache.clear();
417    }
418
419    pub fn alive_count(&self) -> usize {
420        self.cache.alive_count()
421    }
422}