nightshade 0.13.1

A cross-platform data-oriented game engine.
Documentation
mod constructor;
mod execute;
mod prepare;

use crate::ecs::sprite::components::{SpriteBlendMode, SpriteStencilMode};
use crate::ecs::world::World;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use crate::render::wgpu::sprite_texture_atlas::SpriteTextureAtlas;
use nalgebra_glm::{Mat4, Vec2, Vec4};

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct SpriteInstance {
    pub position: Vec2,
    pub size: Vec2,
    pub uv_min: Vec2,
    pub uv_max: Vec2,
    pub color: Vec4,
    pub rotation_scale: Vec4,
    pub anchor: Vec2,
    pub depth: f32,
    pub texture_slot: u32,
    pub texture_slot2: u32,
    pub blend_factor: f32,
    pub gradient_type: u32,
    pub gradient_param_a: f32,
    pub gradient_param_b: f32,
    pub advanced_blend_mode: u32,
    pub _padding: [f32; 2],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct SpriteVertex {
    pub offset: Vec2,
    pub uv: Vec2,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GlobalUniforms {
    pub view_projection: Mat4,
    pub screen_size: Vec2,
    pub atlas_slots_per_row: f32,
    pub atlas_slot_uv_size: f32,
}

struct SpriteDrawBatch {
    blend_mode: SpriteBlendMode,
    stencil_mode: SpriteStencilMode,
    stencil_reference: u32,
    clip_rect: Option<[f32; 4]>,
    instance_start: u32,
    instance_count: u32,
}

pub struct SpritePass {
    pipelines_normal: [wgpu::RenderPipeline; 4],
    pipelines_stencil_write: [wgpu::RenderPipeline; 4],
    pipelines_stencil_test: [wgpu::RenderPipeline; 4],
    pipeline_advanced_normal: wgpu::RenderPipeline,
    pipeline_advanced_stencil_test: wgpu::RenderPipeline,
    global_uniform_buffer: wgpu::Buffer,
    instance_buffer: wgpu::Buffer,
    vertex_buffer: wgpu::Buffer,
    index_buffer: wgpu::Buffer,
    uniform_bind_group: wgpu::BindGroup,
    texture_bind_group_layout: wgpu::BindGroupLayout,
    texture_bind_group: Option<wgpu::BindGroup>,
    background_bind_group_layout: wgpu::BindGroupLayout,
    background_texture: wgpu::Texture,
    background_view: wgpu::TextureView,
    background_sampler: wgpu::Sampler,
    background_bind_group: Option<wgpu::BindGroup>,
    background_texture_size: (u32, u32),
    max_instances: usize,
    instance_data: Vec<SpriteInstance>,
    blend_modes: Vec<SpriteBlendMode>,
    stencil_modes: Vec<SpriteStencilMode>,
    stencil_references: Vec<u8>,
    clip_rects: Vec<Option<[f32; 4]>>,
    draw_batches: Vec<SpriteDrawBatch>,
    atlas: SpriteTextureAtlas,
    stencil_texture: wgpu::Texture,
    stencil_view: wgpu::TextureView,
    stencil_texture_size: (u32, u32),
    cached_view_projection: Option<Mat4>,
    cached_surface_width: u32,
    cached_surface_height: u32,
    sort_indices: Vec<usize>,
    sorted_instances_scratch: Vec<SpriteInstance>,
    sorted_blend_modes_scratch: Vec<SpriteBlendMode>,
    sorted_stencil_modes_scratch: Vec<SpriteStencilMode>,
    sorted_stencil_references_scratch: Vec<u8>,
    sorted_clip_rects_scratch: Vec<Option<[f32; 4]>>,
}

struct SpritePipelineConfig<'a> {
    layout: &'a wgpu::PipelineLayout,
    shader: &'a wgpu::ShaderModule,
    color_format: wgpu::TextureFormat,
    blend_state: wgpu::BlendState,
    depth_stencil: Option<wgpu::DepthStencilState>,
    color_write_mask: wgpu::ColorWrites,
    label: &'a str,
    fragment_entry: &'a str,
}

fn create_sprite_pipeline(
    device: &wgpu::Device,
    config: &SpritePipelineConfig,
) -> wgpu::RenderPipeline {
    device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
        label: Some(config.label),
        layout: Some(config.layout),
        vertex: wgpu::VertexState {
            module: config.shader,
            entry_point: Some("vs_main"),
            buffers: &[wgpu::VertexBufferLayout {
                array_stride: std::mem::size_of::<SpriteVertex>() as wgpu::BufferAddress,
                step_mode: wgpu::VertexStepMode::Vertex,
                attributes: &[
                    wgpu::VertexAttribute {
                        offset: 0,
                        shader_location: 0,
                        format: wgpu::VertexFormat::Float32x2,
                    },
                    wgpu::VertexAttribute {
                        offset: std::mem::size_of::<Vec2>() as wgpu::BufferAddress,
                        shader_location: 1,
                        format: wgpu::VertexFormat::Float32x2,
                    },
                ],
            }],
            compilation_options: wgpu::PipelineCompilationOptions::default(),
        },
        primitive: wgpu::PrimitiveState {
            topology: wgpu::PrimitiveTopology::TriangleList,
            strip_index_format: None,
            front_face: wgpu::FrontFace::Ccw,
            cull_mode: None,
            polygon_mode: wgpu::PolygonMode::Fill,
            unclipped_depth: false,
            conservative: false,
        },
        depth_stencil: config.depth_stencil.clone(),
        multisample: wgpu::MultisampleState {
            count: 1,
            mask: !0,
            alpha_to_coverage_enabled: false,
        },
        fragment: Some(wgpu::FragmentState {
            module: config.shader,
            entry_point: Some(config.fragment_entry),
            targets: &[Some(wgpu::ColorTargetState {
                format: config.color_format,
                blend: Some(config.blend_state),
                write_mask: config.color_write_mask,
            })],
            compilation_options: wgpu::PipelineCompilationOptions::default(),
        }),
        multiview_mask: None,
        cache: None,
    })
}

fn stencil_depth_state_normal() -> wgpu::DepthStencilState {
    wgpu::DepthStencilState {
        format: wgpu::TextureFormat::Depth24PlusStencil8,
        depth_write_enabled: Some(false),
        depth_compare: Some(wgpu::CompareFunction::Always),
        stencil: wgpu::StencilState {
            front: wgpu::StencilFaceState {
                compare: wgpu::CompareFunction::Always,
                fail_op: wgpu::StencilOperation::Keep,
                depth_fail_op: wgpu::StencilOperation::Keep,
                pass_op: wgpu::StencilOperation::Keep,
            },
            back: wgpu::StencilFaceState {
                compare: wgpu::CompareFunction::Always,
                fail_op: wgpu::StencilOperation::Keep,
                depth_fail_op: wgpu::StencilOperation::Keep,
                pass_op: wgpu::StencilOperation::Keep,
            },
            read_mask: 0,
            write_mask: 0,
        },
        bias: wgpu::DepthBiasState::default(),
    }
}

fn stencil_depth_state_write() -> wgpu::DepthStencilState {
    wgpu::DepthStencilState {
        format: wgpu::TextureFormat::Depth24PlusStencil8,
        depth_write_enabled: Some(false),
        depth_compare: Some(wgpu::CompareFunction::Always),
        stencil: wgpu::StencilState {
            front: wgpu::StencilFaceState {
                compare: wgpu::CompareFunction::Always,
                fail_op: wgpu::StencilOperation::Keep,
                depth_fail_op: wgpu::StencilOperation::Keep,
                pass_op: wgpu::StencilOperation::Replace,
            },
            back: wgpu::StencilFaceState {
                compare: wgpu::CompareFunction::Always,
                fail_op: wgpu::StencilOperation::Keep,
                depth_fail_op: wgpu::StencilOperation::Keep,
                pass_op: wgpu::StencilOperation::Replace,
            },
            read_mask: 0xFF,
            write_mask: 0xFF,
        },
        bias: wgpu::DepthBiasState::default(),
    }
}

fn stencil_depth_state_test() -> wgpu::DepthStencilState {
    wgpu::DepthStencilState {
        format: wgpu::TextureFormat::Depth24PlusStencil8,
        depth_write_enabled: Some(false),
        depth_compare: Some(wgpu::CompareFunction::Always),
        stencil: wgpu::StencilState {
            front: wgpu::StencilFaceState {
                compare: wgpu::CompareFunction::Equal,
                fail_op: wgpu::StencilOperation::Keep,
                depth_fail_op: wgpu::StencilOperation::Keep,
                pass_op: wgpu::StencilOperation::Keep,
            },
            back: wgpu::StencilFaceState {
                compare: wgpu::CompareFunction::Equal,
                fail_op: wgpu::StencilOperation::Keep,
                depth_fail_op: wgpu::StencilOperation::Keep,
                pass_op: wgpu::StencilOperation::Keep,
            },
            read_mask: 0xFF,
            write_mask: 0,
        },
        bias: wgpu::DepthBiasState::default(),
    }
}

impl SpritePass {
    pub fn atlas_view(&self) -> &wgpu::TextureView {
        &self.atlas.view
    }

    pub fn atlas_sampler(&self) -> &wgpu::Sampler {
        &self.atlas.sampler
    }

    pub fn atlas_slots_per_row(&self) -> u32 {
        self.atlas.slots_per_row
    }

    pub fn upload_texture_to_atlas(
        &mut self,
        queue: &wgpu::Queue,
        slot: u32,
        rgba_data: &[u8],
        width: u32,
        height: u32,
    ) {
        self.atlas
            .upload_texture(queue, slot, rgba_data, width, height);
    }
}

impl PassNode<World> for SpritePass {
    fn name(&self) -> &str {
        "sprite_pass"
    }

    fn reads(&self) -> Vec<&str> {
        vec![]
    }

    fn writes(&self) -> Vec<&str> {
        vec![]
    }

    fn reads_writes(&self) -> Vec<&str> {
        vec!["color"]
    }

    fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, configs: &World) {
        if let Some((width, height)) = configs.resources.window.cached_viewport_size {
            self.ensure_stencil_texture(device, width, height);
            self.prepare_sprites(configs, width, height);
            self.write_buffers(queue, width, height);
        }
    }

    fn execute<'r, 'e>(
        &mut self,
        context: PassExecutionContext<'r, 'e, World>,
    ) -> Result<
        Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
        crate::render::wgpu::rendergraph::RenderGraphError,
    > {
        let color_view = context.get_texture_view("color")?;
        let color_texture = context.get_texture("color").ok();

        self.render_sprites(
            context.encoder,
            color_view,
            color_texture,
            context.device,
            self.cached_surface_width,
            self.cached_surface_height,
        );

        Ok(vec![])
    }
}