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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
//! Managing [GPU shaders](https://en.wikipedia.org/wiki/Shader).

use crate::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use zaplib_shader_compiler::error::ParseError;
use zaplib_shader_compiler::span::{CodeFragmentId, Span};
use zaplib_shader_compiler::ty::Ty;
use zaplib_shader_compiler::{Decl, ShaderAst};

/// Contains all information necessary to build a shader.
/// Define a new shader.
///
/// Pass in a [`Geometry`] which gets used for instancing (e.g. a quad or a
/// cube).
///
/// The different [`CodeFragment`]s are appended together (but preserving their
/// filename/line/column information for error messages). They are split out
/// into `base_code_fragments` and `main_code_fragment` purely for
/// convenience. (We could instead have used a single [`slice`] but they are
/// annoying to get through concatenation..)
///
/// TODO(JP): Would be good to instead compile shaders beforehand, ie. during
/// compile time. Should look into that at some point.
pub struct Shader {
    /// The [`Geometry`] that we will draw with, if any. Can be overridden using [`DrawCallProps::gpu_geometry`].
    pub build_geom: Option<fn() -> Geometry>,
    /// A bunch of [`CodeFragment`]s that will get concatenated.
    pub code_to_concatenate: &'static [CodeFragment],
    /// The id of the shader (index into [`Cx::shaders`]), or [`Shader::UNCOMPILED_SHADER_ID`] if uninitialized.
    /// You should never read or modify this manually (see TODO below).
    ///
    /// TODO(JP): This shouldn't be public, but right now that's necessary for using [`Shader::DEFAULT`]. We might want to
    /// switch to a `define_shader` helper function once we can pass function pointers to `const` functions (see
    /// <https://github.com/rust-lang/rust/issues/63997> and <https://github.com/rust-lang/rust/issues/57563>).
    pub shader_id: AtomicUsize,
}

impl Shader {
    /// TODO(JP): We might want to switch to a `define_shader` helper function once we can pass function pointers
    /// to `const` functions (see <https://github.com/rust-lang/rust/issues/63997> and
    /// <https://github.com/rust-lang/rust/issues/57563>).
    ///
    /// We suppress `clippy::declare_interior_mutable_const` here since we don't actually want shader_id in this constant
    /// to be editable.
    #[allow(clippy::declare_interior_mutable_const)]
    pub const DEFAULT: Shader =
        Shader { build_geom: None, code_to_concatenate: &[], shader_id: AtomicUsize::new(Self::UNCOMPILED_SHADER_ID) };

    const UNCOMPILED_SHADER_ID: usize = usize::MAX;

    pub fn update(&'static self, cx: &mut Cx, new_code_to_concatenate: &[CodeFragment]) -> Result<(), ParseError> {
        let shader_id = cx.get_shader_id(self);

        let shader = &mut cx.shaders[shader_id];
        let shader_ast = cx.shader_ast_generator.generate_shader_ast(new_code_to_concatenate)?;
        if shader.mapping != CxShaderMapping::from_shader_ast(shader_ast.clone()) {
            return Err(ParseError {
                span: Span { code_fragment_id: CodeFragmentId(0), start: 0, end: 0 },
                message: "Mismatch in shader mapping".to_string(),
            });
        }
        shader.shader_ast = Some(shader_ast);
        cx.shader_recompile_ids.push(shader_id);

        Ok(())
    }
}

/// Contains information of a [`CxShader`] of what instances, instances, textures
/// and so on it contains. That information can then be used to modify a [`Shader`
/// or [`DrawCall`].
#[derive(Debug, Default, Clone, PartialEq)]
pub(crate) struct CxShaderMapping {
    /// Contains information about the special "rect_pos" and "rect_size" fields.
    /// See [`RectInstanceProps`].
    pub(crate) rect_instance_props: RectInstanceProps,
    /// Special structure for user-level uniforms.
    pub(crate) user_uniform_props: UniformProps,
    /// Special structure for reading/editing instance properties.
    pub(crate) instance_props: InstanceProps,
    /// Special structure for reading/editing geometry properties.
    pub(crate) geometry_props: InstanceProps,
    /// Raw definition of all textures.
    pub(crate) textures: Vec<PropDef>,
    /// Raw definition of all geometries.
    #[cfg(target_os = "windows")]
    pub(crate) geometries: Vec<PropDef>,
    /// Raw definition of all instances.
    #[cfg(target_os = "windows")]
    pub(crate) instances: Vec<PropDef>,
    /// Raw definition of all user-level uniforms.
    #[cfg(any(target_arch = "wasm32", target_os = "linux"))]
    pub(crate) user_uniforms: Vec<PropDef>,
    /// Raw definition of all framework-level uniforms that get set per [`DrawCall`].
    #[cfg(any(target_arch = "wasm32", target_os = "linux"))]
    pub(crate) draw_uniforms: Vec<PropDef>,
    /// Raw definition of all framework-level uniforms that get set per [`View`].
    #[cfg(any(target_arch = "wasm32", target_os = "linux"))]
    pub(crate) view_uniforms: Vec<PropDef>,
    /// Raw definition of all framework-level uniforms that get set per [`Pass`].
    #[cfg(any(target_arch = "wasm32", target_os = "linux"))]
    pub(crate) pass_uniforms: Vec<PropDef>,
}

impl CxShaderMapping {
    fn from_shader_ast(shader_ast: ShaderAst) -> Self {
        let mut instances = Vec::new();
        let mut geometries = Vec::new();
        let mut user_uniforms = Vec::new();
        let mut draw_uniforms = Vec::new();
        let mut view_uniforms = Vec::new();
        let mut pass_uniforms = Vec::new();
        let mut textures = Vec::new();
        for decl in shader_ast.decls {
            match decl {
                Decl::Geometry(decl) => {
                    let prop_def = PropDef { name: decl.ident.to_string(), ty: decl.ty_expr.ty.borrow().clone().unwrap() };
                    geometries.push(prop_def);
                }
                Decl::Instance(decl) => {
                    let prop_def = PropDef { name: decl.ident.to_string(), ty: decl.ty_expr.ty.borrow().clone().unwrap() };
                    instances.push(prop_def);
                }
                Decl::Uniform(decl) => {
                    let prop_def = PropDef { name: decl.ident.to_string(), ty: decl.ty_expr.ty.borrow().clone().unwrap() };
                    match decl.block_ident {
                        Some(bi) if bi.with(|string| string == "draw") => {
                            draw_uniforms.push(prop_def);
                        }
                        Some(bi) if bi.with(|string| string == "view") => {
                            view_uniforms.push(prop_def);
                        }
                        Some(bi) if bi.with(|string| string == "pass") => {
                            pass_uniforms.push(prop_def);
                        }
                        None => {
                            user_uniforms.push(prop_def);
                        }
                        _ => (),
                    }
                }
                Decl::Texture(decl) => {
                    let prop_def = PropDef { name: decl.ident.to_string(), ty: decl.ty_expr.ty.borrow().clone().unwrap() };
                    textures.push(prop_def);
                }
                _ => (),
            }
        }

        CxShaderMapping {
            rect_instance_props: RectInstanceProps::construct(&instances),
            user_uniform_props: UniformProps::construct(&user_uniforms),
            instance_props: InstanceProps::construct(&instances),
            geometry_props: InstanceProps::construct(&geometries),
            textures,
            #[cfg(target_os = "windows")]
            instances,
            #[cfg(target_os = "windows")]
            geometries,
            #[cfg(any(target_arch = "wasm32", target_os = "linux"))]
            pass_uniforms,
            #[cfg(any(target_arch = "wasm32", target_os = "linux"))]
            view_uniforms,
            #[cfg(any(target_arch = "wasm32", target_os = "linux"))]
            draw_uniforms,
            #[cfg(any(target_arch = "wasm32", target_os = "linux"))]
            user_uniforms,
        }
    }
}

/// The raw definition of an input property to a [`Shader`].
#[derive(Debug, Clone, Hash, PartialEq)]
pub(crate) struct PropDef {
    pub(crate) name: String,
    pub(crate) ty: Ty,
}

/// Contains information about the special "rect_pos" and "rect_size" fields.
/// These fields describe the typical rectangles drawn in [`crate::QuadIns`]. It is
/// useful to have generalized access to them, so we can e.g. move a whole bunch
/// of rectangles at the same time, e.g. for alignment in [`CxLayoutBox`].
/// TODO(JP): We might want to consider instead doing bulk moves using [`DrawCall`-
/// or [`View`]-level uniforms.
#[derive(Debug, Default, Clone, PartialEq)]
pub(crate) struct RectInstanceProps {
    pub(crate) rect_pos: Option<usize>,
    pub(crate) rect_size: Option<usize>,
}
impl RectInstanceProps {
    fn construct(instances: &[PropDef]) -> RectInstanceProps {
        let mut rect_pos = None;
        let mut rect_size = None;
        let mut slot = 0;
        for inst in instances {
            match inst.name.as_ref() {
                "rect_pos" => rect_pos = Some(slot),
                "rect_size" => rect_size = Some(slot),
                _ => (),
            }
            slot += inst.ty.size(); //sg.get_type_slots(&inst.ty);
        }
        RectInstanceProps { rect_pos, rect_size }
    }
}

/// Represents an "instance" GPU input in a [`Shader`].
///
/// TODO(JP): Merge this into [`NamedProp`].
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct InstanceProp {
    pub(crate) name: String,
    pub(crate) slots: usize,
}

/// Represents all "instance" GPU inputs in a [`Shader`].
///
/// TODO(JP): Merge this into [`NamedProps`].
#[derive(Debug, Default, Clone, PartialEq)]
pub(crate) struct InstanceProps {
    pub(crate) props: Vec<InstanceProp>,
    pub(crate) total_slots: usize,
}

/// Represents all "uniform" GPU inputs in a [`Shader`].
///
/// TODO(JP): Merge this into [`NamedProps`].
#[derive(Debug, Default, Clone, PartialEq)]
pub(crate) struct UniformProps {
    pub(crate) total_slots: usize,
}

/// A generic representation of any kind of [`Shader`] input (instance/uniform/geometry).
#[cfg(target_os = "windows")]
#[derive(Debug, Clone)]
pub(crate) struct NamedProp {
    pub(crate) offset: usize,
    pub(crate) slots: usize,
}

/// A generic representation of a list of [`Shader`] inputs (instance/uniform/geometry).
#[cfg(target_os = "windows")]
#[derive(Debug, Default, Clone)]
pub(crate) struct NamedProps {
    pub(crate) props: Vec<NamedProp>,
}

#[cfg(target_os = "windows")]
impl NamedProps {
    pub(crate) fn construct(in_props: &[PropDef]) -> NamedProps {
        let mut offset = 0;
        let out_props = in_props
            .iter()
            .map(|prop| {
                let slots = prop.ty.size();
                let prop = NamedProp { offset, slots };
                offset += slots;
                prop
            })
            .collect();
        NamedProps { props: out_props }
    }
}

impl InstanceProps {
    fn construct(in_props: &[PropDef]) -> InstanceProps {
        let mut offset = 0;
        let out_props = in_props
            .iter()
            .map(|prop| {
                let slots = prop.ty.size();
                let prop = InstanceProp { name: prop.name.clone(), slots };
                offset += slots;
                prop
            })
            .collect();
        InstanceProps { props: out_props, total_slots: offset }
    }
}

impl UniformProps {
    pub fn construct(in_props: &[PropDef]) -> UniformProps {
        UniformProps { total_slots: in_props.iter().map(|prop| prop.ty.size()).sum() }
    }
}

/// The actual shader information, which gets stored on [`Cx`]. Once compiled the
/// [`ShaderAst`] will be removed, and the [`CxPlatformShader`] (platform-specific
/// part of the compiled shader) gets set.
#[derive(Default)]
pub(crate) struct CxShader {
    pub(crate) name: String,
    pub(crate) gpu_geometry: Option<GpuGeometry>,
    pub(crate) platform: Option<CxPlatformShader>,
    pub(crate) mapping: CxShaderMapping,
    pub(crate) shader_ast: Option<ShaderAst>,
}

impl Cx {
    /// Get an individual [`Shader`] from a static [`Shader`].
    ///
    /// For more information on what [`LocationHash`] is used for here, see [`Shader`].
    pub(crate) fn get_shader_id(&mut self, shader: &'static Shader) -> usize {
        let shader_id = shader.shader_id.load(Ordering::Relaxed);
        if shader_id != Shader::UNCOMPILED_SHADER_ID {
            shader_id
        } else {
            // Use the last code fragment as the shader name.
            let main_code_fragment = shader.code_to_concatenate.last().expect("No code fragments found");
            match self.shader_ast_generator.generate_shader_ast(shader.code_to_concatenate) {
                Err(err) => panic!("{}", err.format_for_console(shader.code_to_concatenate)),
                Ok(shader_ast) => {
                    let gpu_geometry = shader.build_geom.map(|build_geom| GpuGeometry::new(self, (build_geom)()));

                    let shader_id = self.shaders.len();
                    self.shaders.push(CxShader {
                        name: main_code_fragment.name_line_col_at_offset(0),
                        gpu_geometry,
                        mapping: CxShaderMapping::from_shader_ast(shader_ast.clone()),
                        platform: None,
                        shader_ast: Some(shader_ast),
                    });
                    self.shader_recompile_ids.push(shader_id);

                    shader.shader_id.store(shader_id, Ordering::Relaxed);

                    shader_id
                }
            }
        }
    }
}