blue_engine_core/
definition.rs

1/*
2 * Blue Engine by Elham Aryanpur
3 *
4 * The license is same as the one on the root.
5*/
6
7use image::GenericImageView;
8use wgpu::{BindGroupLayout, Sampler, Texture, TextureView, util::DeviceExt};
9
10use crate::{
11    InstanceRaw, UnsignedIntType,
12    prelude::{Shaders, Textures, UniformBuffers, Vertex},
13};
14
15/// Container for pipeline values. Each pipeline takes only 1 vertex shader,
16/// 1 fragment shader, 1 texture data, and optionally a vector of uniform data.
17#[derive(Debug)]
18pub struct Pipeline {
19    /// the shader buffer that's sent to the gpu
20    pub shader: PipelineData<crate::Shaders>,
21    /// The vertex buffer that's sent to the gpu. This includes indices as well
22    pub vertex_buffer: PipelineData<VertexBuffers>,
23    /// The texture that's sent to the gpu.
24    pub texture: PipelineData<crate::Textures>,
25    /// the Uniform buffers that are sent to the gpu
26    pub uniform: PipelineData<Option<crate::UniformBuffers>>,
27}
28unsafe impl Send for Pipeline {}
29unsafe impl Sync for Pipeline {}
30
31/// Container for pipeline data. Allows for sharing resources with other objects
32#[derive(Debug)]
33pub enum PipelineData<T> {
34    /// No data, just a reference to a buffer
35    Copy(std::sync::Arc<str>),
36    /// The actual data
37    Data(T),
38}
39
40/// Container for vertex and index buffer
41#[derive(Debug)]
42pub struct VertexBuffers {
43    /// An array of vertices. A vertex is a point in 3D space containing
44    /// an X, Y, and a Z coordinate between -1 and +1
45    pub vertex_buffer: wgpu::Buffer,
46    /// An array of indices. Indices are a way to reuse vertices,
47    /// this in turn helps greatly in reduction of amount of vertices needed to be sent to the GPU
48    pub index_buffer: wgpu::Buffer,
49    /// The length of the vertex buffer
50    pub length: u32,
51}
52unsafe impl Send for VertexBuffers {}
53unsafe impl Sync for VertexBuffers {}
54
55/// Defines how the texture data is
56#[derive(Debug, Clone)]
57pub enum TextureData {
58    /// the texture file bytes directly
59    Bytes(Vec<u8>),
60    /// the texture as a [`image::DynamicImage`]
61    Image(image::DynamicImage),
62    /// path to a texture file to load
63    Path(String),
64}
65unsafe impl Send for TextureData {}
66unsafe impl Sync for TextureData {}
67
68/// Defines how the borders of texture would look like
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum TextureMode {
71    /// Expands the texture to fit the object
72    Clamp,
73    /// Repeats the texture instead of stretching
74    Repeat,
75    /// Repeats the texture, but mirrors it on edges
76    MirrorRepeat,
77}
78unsafe impl Send for TextureMode {}
79unsafe impl Sync for TextureMode {}
80
81/// These definitions are taken from wgpu API docs
82#[derive(Debug, Clone, Copy)]
83pub struct ShaderSettings {
84    // ===== PRIMITIVE ===== //
85    /// The primitive topology used to interpret vertices
86    pub topology: crate::ShaderPrimitive,
87    /// When drawing strip topologies with indices, this is the
88    /// required format for the index buffer. This has no effect
89    /// on non-indexed or non-strip draws.
90    pub strip_index_format: Option<crate::IndexFormat>,
91    /// The face to consider the front for the purpose of
92    /// culling and stencil operations.
93    pub front_face: crate::FrontFace,
94    /// The face culling mode
95    pub cull_mode: Option<crate::CullMode>,
96    /// Controls the way each polygon is rasterized. Can be
97    /// either `Fill` (default), `Line` or `Point`
98    ///
99    /// Setting this to something other than `Fill` requires
100    /// `NON_FILL_POLYGON_MODE` feature to be enabled
101    pub polygon_mode: crate::PolygonMode,
102    /// If set to true, the polygon depth is clamped to 0-1
103    /// range instead of being clipped.
104    ///
105    /// Enabling this requires the `DEPTH_CLAMPING` feature
106    /// to be enabled
107    pub clamp_depth: bool,
108    /// If set to true, the primitives are rendered with
109    /// conservative overestimation. I.e. any rastered
110    /// pixel touched by it is filled. Only valid for PolygonMode::Fill!
111    ///
112    /// Enabling this requires `CONSERVATIVE_RASTERIZATION`
113    /// features to be enabled.
114    pub conservative: bool,
115    // ===== Multisample ===== //
116    /// The number of samples calculated per pixel (for MSAA).
117    /// For non-multisampled textures, this should be `1`
118    pub count: u32,
119    /// Bitmask that restricts the samples of a pixel modified
120    /// by this pipeline. All samples can be enabled using the
121    /// value `!0`
122    pub mask: u64,
123    /// When enabled, produces another sample mask per pixel
124    /// based on the alpha output value, that is ANDead with the
125    /// sample_mask and the primitive coverage to restrict the
126    /// set of samples affected by a primitive.
127
128    /// The implicit mask produced for alpha of zero is guaranteed
129    /// to be zero, and for alpha of one is guaranteed to be all
130    /// 1-s.
131    pub alpha_to_coverage_enabled: bool,
132}
133impl Default for ShaderSettings {
134    fn default() -> Self {
135        Self {
136            topology: wgpu::PrimitiveTopology::TriangleList,
137            strip_index_format: None,
138            front_face: wgpu::FrontFace::Ccw,
139            cull_mode: Some(wgpu::Face::Back),
140            polygon_mode: wgpu::PolygonMode::Fill,
141            clamp_depth: false,
142            conservative: false,
143            count: 1,
144            mask: !0,
145            alpha_to_coverage_enabled: true,
146        }
147    }
148}
149unsafe impl Send for ShaderSettings {}
150unsafe impl Sync for ShaderSettings {}
151
152/// This function helps in converting pixel value to the value that is between -1 and +1
153pub fn pixel_to_cartesian(value: f32, max: u32) -> f32 {
154    let mut result = value / max as f32;
155
156    if value == max as f32 {
157        result = 0.0;
158    } else if result < max as f32 / 2.0 {
159    }
160
161    if result > -1.0 { result } else { -1.0 }
162}
163
164impl crate::prelude::Renderer {
165    /// Creates a new render pipeline. Could be thought of as like materials in game engines.
166    pub fn build_pipeline(
167        &mut self,
168        shader: Shaders,
169        vertex_buffer: VertexBuffers,
170        texture: Textures,
171        uniform: Option<UniformBuffers>,
172    ) -> Pipeline {
173        Pipeline {
174            shader: PipelineData::Data(shader),
175            vertex_buffer: PipelineData::Data(vertex_buffer),
176            texture: PipelineData::Data(texture),
177            uniform: PipelineData::Data(uniform),
178        }
179    }
180
181    /// Creates a shader group, the input must be spir-v compiled vertex and fragment shader
182    pub fn build_shader(
183        &mut self,
184        name: impl AsRef<str>,
185        shader_source: String,
186        uniform_layout: Option<&BindGroupLayout>,
187        settings: ShaderSettings,
188    ) -> Shaders {
189        let shader = self
190            .device
191            .create_shader_module(wgpu::ShaderModuleDescriptor {
192                label: Some(format!("{} Shader", name.as_ref()).as_str()),
193                source: wgpu::ShaderSource::Wgsl(shader_source.into()),
194            });
195
196        let mut bind_group_layouts = vec![
197            &self.texture_bind_group_layout,
198            &self.default_uniform_bind_group_layout,
199        ];
200        if let Some(uniform_layout) = uniform_layout {
201            bind_group_layouts.push(uniform_layout);
202        }
203
204        let render_pipeline_layout =
205            self.device
206                .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
207                    label: Some("Render Pipeline Layout"),
208                    bind_group_layouts: bind_group_layouts.as_slice(),
209                    push_constant_ranges: &[],
210                });
211
212        let render_pipeline = self
213            .device
214            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
215                label: Some(name.as_ref()),
216                layout: Some(&render_pipeline_layout),
217                vertex: wgpu::VertexState {
218                    module: &shader,
219                    entry_point: Some("vs_main"),
220                    buffers: &[Vertex::desc(), InstanceRaw::desc()],
221                    compilation_options: wgpu::PipelineCompilationOptions::default(),
222                },
223                fragment: Some(wgpu::FragmentState {
224                    module: &shader,
225                    entry_point: Some("fs_main"),
226                    targets: &[Some(wgpu::ColorTargetState {
227                        format: self.config.format,
228                        write_mask: wgpu::ColorWrites::ALL,
229                        blend: Some(wgpu::BlendState::ALPHA_BLENDING),
230                    })],
231                    compilation_options: wgpu::PipelineCompilationOptions::default(),
232                }),
233                primitive: wgpu::PrimitiveState {
234                    topology: settings.topology,
235                    strip_index_format: settings.strip_index_format,
236                    front_face: settings.front_face,
237                    cull_mode: settings.cull_mode, //Some(wgpu::Face::Back),
238                    polygon_mode: settings.polygon_mode,
239                    conservative: settings.conservative,
240                    //clamp_depth: settings.clamp_depth,
241                    unclipped_depth: false,
242                },
243                depth_stencil: Some(wgpu::DepthStencilState {
244                    format: crate::DEPTH_FORMAT,
245                    depth_write_enabled: true,
246                    depth_compare: wgpu::CompareFunction::Less,
247                    stencil: wgpu::StencilState::default(),
248                    bias: wgpu::DepthBiasState::default(),
249                }),
250                multisample: wgpu::MultisampleState {
251                    count: settings.count,
252                    mask: settings.mask,
253                    alpha_to_coverage_enabled: settings.alpha_to_coverage_enabled,
254                },
255                multiview: None,
256                cache: None,
257            });
258
259        render_pipeline
260    }
261
262    /// Creates a new texture data
263    pub fn build_texture(
264        &mut self,
265        name: impl AsRef<str>,
266        texture_data: TextureData,
267        texture_mode: TextureMode,
268    ) -> Result<Textures, crate::error::Error> {
269        let mode: wgpu::AddressMode = match texture_mode {
270            TextureMode::Clamp => wgpu::AddressMode::Repeat,
271            TextureMode::Repeat => wgpu::AddressMode::MirrorRepeat,
272            TextureMode::MirrorRepeat => wgpu::AddressMode::ClampToEdge,
273        };
274
275        let img = match texture_data {
276            TextureData::Bytes(data) => image::load_from_memory(data.as_slice())?,
277            TextureData::Image(data) => data,
278            TextureData::Path(path) => image::open(path)?,
279        };
280
281        fn rgba_to_bgra_pixels_par(
282            mut buf: image::ImageBuffer<image::Rgba<u8>, Vec<u8>>,
283        ) -> image::ImageBuffer<image::Rgba<u8>, Vec<u8>> {
284            buf.enumerate_pixels_mut().for_each(|(_, _, px)| {
285                let data = px.0;
286                *px = image::Rgba([data[2], data[1], data[0], data[3]]);
287            });
288            buf
289        }
290
291        let pixels = match self.config.format {
292            wgpu::TextureFormat::Bgra8Unorm => rgba_to_bgra_pixels_par(img.to_rgba8()),
293            wgpu::TextureFormat::Bgra8UnormSrgb => rgba_to_bgra_pixels_par(img.to_rgba8()),
294            _ => img.to_rgba8(),
295        };
296        let dimensions = img.dimensions();
297
298        let size = wgpu::Extent3d {
299            width: dimensions.0,
300            height: dimensions.1,
301            depth_or_array_layers: 1,
302        };
303        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
304            label: Some(name.as_ref()),
305            size,
306            mip_level_count: 1,
307            sample_count: 1,
308            dimension: wgpu::TextureDimension::D2,
309            format: self.config.format,
310            usage: wgpu::TextureUsages::TEXTURE_BINDING
311                | wgpu::TextureUsages::COPY_DST
312                | wgpu::TextureUsages::COPY_SRC
313                | wgpu::TextureUsages::RENDER_ATTACHMENT,
314            view_formats: &[],
315        });
316
317        self.queue.write_texture(
318            wgpu::TexelCopyTextureInfo {
319                texture: &texture,
320                mip_level: 0,
321                origin: wgpu::Origin3d::ZERO,
322                aspect: wgpu::TextureAspect::All,
323            },
324            &pixels,
325            wgpu::TexelCopyBufferLayout {
326                offset: 0,
327                bytes_per_row: Some(4 * dimensions.0),
328                rows_per_image: Some(dimensions.1),
329            },
330            size,
331        );
332
333        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
334        let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
335            address_mode_u: mode,
336            address_mode_v: mode,
337            address_mode_w: mode,
338            mag_filter: wgpu::FilterMode::Linear,
339            min_filter: wgpu::FilterMode::Nearest,
340            mipmap_filter: wgpu::FilterMode::Nearest,
341            ..Default::default()
342        });
343
344        let diffuse_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
345            layout: &self.texture_bind_group_layout,
346            label: Some("Diffuse Bind Group"),
347            entries: &[
348                wgpu::BindGroupEntry {
349                    binding: 0,
350                    resource: wgpu::BindingResource::TextureView(&view),
351                },
352                wgpu::BindGroupEntry {
353                    binding: 1,
354                    resource: wgpu::BindingResource::Sampler(&sampler),
355                },
356            ],
357        });
358
359        Ok(diffuse_bind_group)
360    }
361
362    pub(crate) fn build_depth_buffer(
363        label: impl AsRef<str>,
364        device: &wgpu::Device,
365        config: &wgpu::SurfaceConfiguration,
366    ) -> (Texture, TextureView, Sampler) {
367        let size = wgpu::Extent3d {
368            width: config.width,
369            height: config.height,
370            depth_or_array_layers: 1,
371        };
372        let desc = wgpu::TextureDescriptor {
373            label: Some(label.as_ref()),
374            size,
375            mip_level_count: 1,
376            sample_count: 1,
377            dimension: wgpu::TextureDimension::D2,
378            format: crate::DEPTH_FORMAT,
379            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
380            view_formats: &[wgpu::TextureFormat::Depth32Float],
381        };
382        let texture = device.create_texture(&desc);
383
384        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
385        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
386            address_mode_u: wgpu::AddressMode::ClampToEdge,
387            address_mode_v: wgpu::AddressMode::ClampToEdge,
388            address_mode_w: wgpu::AddressMode::ClampToEdge,
389            mag_filter: wgpu::FilterMode::Linear,
390            min_filter: wgpu::FilterMode::Linear,
391            mipmap_filter: wgpu::FilterMode::Nearest,
392            compare: Some(wgpu::CompareFunction::LessEqual),
393            lod_min_clamp: 0.0,
394            lod_max_clamp: 100.0,
395            ..Default::default()
396        });
397
398        (texture, view, sampler)
399    }
400
401    /// Creates a new uniform buffer part
402    ///
403    /// This function doesn't build the entire uniform buffers list, but rather only one of them
404    pub fn build_uniform_buffer_part<T: bytemuck::Zeroable + bytemuck::Pod>(
405        &self,
406        name: impl AsRef<str>,
407        value: T,
408    ) -> wgpu::Buffer {
409        self.device
410            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
411                label: Some(name.as_ref()),
412                contents: bytemuck::cast_slice(&[value]),
413                usage: wgpu::BufferUsages::UNIFORM,
414            })
415    }
416
417    /// Creates a new uniform buffer group, according to a list of types
418    pub fn build_uniform_buffer(
419        &mut self,
420        uniforms: &[wgpu::Buffer],
421    ) -> (UniformBuffers, BindGroupLayout) {
422        let mut buffer_entry = Vec::<wgpu::BindGroupEntry>::new();
423        let mut buffer_layout = Vec::<wgpu::BindGroupLayoutEntry>::new();
424
425        for i in 0..uniforms.len() {
426            if let Some(uniform) = uniforms.get(i) {
427                let descriptor = wgpu::BindGroupEntry {
428                    binding: i as u32,
429                    resource: uniform.as_entire_binding(),
430                };
431                buffer_entry.push(descriptor);
432                buffer_layout.push(wgpu::BindGroupLayoutEntry {
433                    binding: i as u32,
434                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
435                    ty: wgpu::BindingType::Buffer {
436                        ty: wgpu::BufferBindingType::Uniform,
437                        has_dynamic_offset: false,
438                        min_binding_size: None,
439                    },
440                    count: None,
441                });
442            }
443        }
444
445        let uniform_bind_group_layout =
446            self.device
447                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
448                    label: Some("uniform dynamic bind group layout"),
449                    entries: buffer_layout.as_slice(),
450                });
451
452        let uniform_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
453            label: Some("Uniform Bind Groups"),
454            layout: &uniform_bind_group_layout,
455            entries: buffer_entry.as_slice(),
456        });
457
458        (uniform_bind_group, uniform_bind_group_layout)
459    }
460
461    /// Creates a new vertex buffer and indices
462    pub fn build_vertex_buffer(
463        &mut self,
464        vertices: &Vec<Vertex>,
465        indices: &Vec<UnsignedIntType>,
466    ) -> VertexBuffers {
467        let vertex_buffer = self
468            .device
469            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
470                label: Some("Vertex Buffer"),
471                contents: bytemuck::cast_slice(vertices.as_slice()),
472                usage: wgpu::BufferUsages::VERTEX,
473            });
474
475        let index_buffer = self
476            .device
477            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
478                label: Some("Index Buffer"),
479                contents: bytemuck::cast_slice(indices.as_slice()),
480                usage: wgpu::BufferUsages::INDEX,
481            });
482
483        VertexBuffers {
484            vertex_buffer,
485            index_buffer,
486            length: indices.len() as u32,
487        }
488    }
489
490    /// Creates a new instance buffer for the object
491    pub fn build_instance(&self, instance_data: Vec<InstanceRaw>) -> wgpu::Buffer {
492        self.device
493            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
494                label: Some("Instance Buffer"),
495                contents: bytemuck::cast_slice(&instance_data),
496                usage: wgpu::BufferUsages::VERTEX,
497            })
498    }
499}
500
501impl crate::SignalStorage {
502    /// Creates a new live event storage
503    pub fn new() -> Self {
504        Self { events: vec![] }
505    }
506
507    /// Adds an event
508    pub fn add_signal(&mut self, key: impl AsRef<str>, event: Box<dyn crate::Signal>) {
509        self.events.push((key.as_ref().to_string(), event));
510    }
511
512    /// Removes an event
513    pub fn remove_signal(&mut self, key: impl AsRef<str>) {
514        self.events.retain(|k| k.0 != key.as_ref());
515    }
516
517    /// Gets an event
518    pub fn get_signal<T: 'static>(
519        &mut self,
520        key: impl AsRef<str>,
521    ) -> Option<Result<&mut T, downcast::TypeMismatch>> {
522        // fetch the event
523        let event = self
524            .events
525            .iter_mut()
526            .find(|k| k.0 == key.as_ref())
527            .map(|k| &mut k.1);
528
529        if let Some(event) = event {
530            // downcast the event
531            let event_type = event.downcast_mut::<T>();
532            Some(event_type)
533        } else {
534            None
535        }
536    }
537}
538
539impl Default for crate::SignalStorage {
540    fn default() -> Self {
541        Self::new()
542    }
543}