1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use crate::{
    core::{
        log::{Log, MessageKind},
        scope_profile,
        sparse::SparseBuffer,
        sstorage::ImmutableString,
    },
    material::shader::{Shader, ShaderResource},
    renderer::{
        cache::CacheEntry,
        framework::{framebuffer::DrawParameters, gpu_program::GpuProgram, state::PipelineState},
    },
};
use fxhash::FxHashMap;
use fyrox_resource::entry::DEFAULT_RESOURCE_LIFETIME;
use fyrox_resource::ResourceStateRef;

pub struct RenderPassData {
    pub program: GpuProgram,
    pub draw_params: DrawParameters,
}

pub struct ShaderSet {
    pub render_passes: FxHashMap<ImmutableString, RenderPassData>,
}

impl ShaderSet {
    pub fn new(state: &mut PipelineState, shader: &Shader) -> Option<Self> {
        let mut map = FxHashMap::default();
        for render_pass in shader.definition.passes.iter() {
            let program_name = format!("{}_{}", shader.definition.name, render_pass.name);
            match GpuProgram::from_source(
                state,
                &program_name,
                &render_pass.vertex_shader,
                &render_pass.fragment_shader,
            ) {
                Ok(gpu_program) => {
                    map.insert(
                        ImmutableString::new(&render_pass.name),
                        RenderPassData {
                            program: gpu_program,
                            draw_params: render_pass.draw_parameters.clone(),
                        },
                    );
                }
                Err(e) => {
                    Log::writeln(
                        MessageKind::Error,
                        format!(
                            "Failed to create {} shader' GPU program. Reason: {:?}",
                            program_name, e
                        ),
                    );
                    return None;
                }
            };
        }

        Some(Self { render_passes: map })
    }
}

#[derive(Default)]
pub struct ShaderCache {
    pub(super) buffer: SparseBuffer<CacheEntry<ShaderSet>>,
}

impl ShaderCache {
    pub fn remove(&mut self, shader: &ShaderResource) {
        let state = shader.state();
        if let ResourceStateRef::Ok(shader_state) = state.get() {
            self.buffer.free(&shader_state.cache_index);
        }
    }

    pub fn get(
        &mut self,
        pipeline_state: &mut PipelineState,
        shader: &ShaderResource,
    ) -> Option<&ShaderSet> {
        scope_profile!();

        let key = shader.key();
        let shader_state = shader.state();

        if let ResourceStateRef::Ok(shader_state) = shader_state.get() {
            if self.buffer.is_index_valid(&shader_state.cache_index) {
                let entry = self.buffer.get_mut(&shader_state.cache_index).unwrap();

                // ShaderSet won't be destroyed while it used.
                entry.time_to_live = DEFAULT_RESOURCE_LIFETIME;

                Some(&entry.value)
            } else {
                let index = self.buffer.spawn(CacheEntry {
                    value: ShaderSet::new(pipeline_state, shader_state)?,
                    time_to_live: DEFAULT_RESOURCE_LIFETIME,
                    value_hash: key as u64,
                });
                shader_state.cache_index.set(index.get());
                Some(&self.buffer.get(&index).unwrap().value)
            }
        } else {
            None
        }
    }

    pub fn update(&mut self, dt: f32) {
        scope_profile!();

        for entry in self.buffer.iter_mut() {
            entry.time_to_live -= dt;
        }

        for i in 0..self.buffer.len() {
            if let Some(entry) = self.buffer.get_raw(i) {
                if entry.time_to_live <= 0.0 {
                    self.buffer.free_raw(i);
                }
            }
        }
    }

    pub fn clear(&mut self) {
        self.buffer.clear();
    }
}