nightshade 0.14.0

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

use crate::ecs::world::World;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use nalgebra_glm::Mat4;

const INITIAL_RECT_CAPACITY: usize = 4096;
const INITIAL_TEXT_VERTEX_CAPACITY: usize = 64000;
const INITIAL_TEXT_INDEX_CAPACITY: usize = 96000;
const INITIAL_TEXT_INSTANCE_CAPACITY: usize = 1024;
const INITIAL_CHARACTER_COLOR_CAPACITY: usize = 10000;

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
struct UiRectInstance {
    position_size: [f32; 4],
    color: [f32; 4],
    border_color: [f32; 4],
    clip_rect: [f32; 4],
    params: [f32; 4],
    shadow_color: [f32; 4],
    shadow_params: [f32; 4],
    effect_params: [f32; 4],
    quad_corner_01: [f32; 4],
    quad_corner_23: [f32; 4],
    effect_kind: u32,
    is_quad: u32,
    _padding: [u32; 2],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
struct TextInstanceData {
    color: [f32; 4],
    outline_color: [f32; 4],
    clip_rect: [f32; 4],
    params: [f32; 4],
    position: [f32; 4],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct UiPassVertex {
    position: [f32; 2],
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct UiTextVertex {
    position: [f32; 3],
    tex_coords: [f32; 2],
    character_index: u32,
    text_instance_index: u32,
    _padding: u32,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct GlobalUniforms {
    projection: Mat4,
}

struct TextDrawCall {
    index_start: u32,
    index_count: u32,
    layer: super::UiLayer,
}

struct LayerDrawGroup {
    rect_start: u32,
    rect_count: u32,
    text_draw_start: usize,
    text_draw_count: usize,
}

#[derive(Clone, Copy, Debug)]
pub(super) struct SlabAlloc {
    pub start: u32,
    pub capacity: u32,
}

#[derive(Clone, Debug)]
pub(super) struct TextSlotEntry {
    pub signature: u64,
    pub vertex_alloc: SlabAlloc,
    pub index_alloc: SlabAlloc,
    pub index_count: u32,
    pub char_color_alloc: SlabAlloc,
}

#[derive(Default)]
pub(super) struct SlabAllocator {
    pub free: Vec<SlabAlloc>,
    pub high_water: u32,
}

impl SlabAllocator {
    pub fn alloc(&mut self, needed: u32) -> SlabAlloc {
        if needed == 0 {
            return SlabAlloc {
                start: 0,
                capacity: 0,
            };
        }
        if let Some(index) = self.free.iter().position(|s| s.capacity >= needed) {
            return self.free.swap_remove(index);
        }
        let start = self.high_water;
        self.high_water += needed;
        SlabAlloc {
            start,
            capacity: needed,
        }
    }

    pub fn free(&mut self, alloc: SlabAlloc) {
        if alloc.capacity > 0 {
            self.free.push(alloc);
        }
    }
}

pub struct UiPass {
    rect_pipeline: wgpu::RenderPipeline,
    text_pipeline: wgpu::RenderPipeline,

    global_uniform_buffer: wgpu::Buffer,
    global_uniform_bind_group: wgpu::BindGroup,

    rect_quad_vertex_buffer: wgpu::Buffer,
    rect_quad_index_buffer: wgpu::Buffer,
    rect_instance_buffer: wgpu::Buffer,
    rect_draw_order_buffer: wgpu::Buffer,
    rect_instance_bind_group_layout: wgpu::BindGroupLayout,
    rect_instance_bind_group: wgpu::BindGroup,
    rect_instance_capacity: usize,
    rect_draw_order_capacity: usize,
    rect_count: u32,
    prev_rect_instances: Vec<UiRectInstance>,

    text_vertex_buffer: wgpu::Buffer,
    text_index_buffer: wgpu::Buffer,
    text_instance_buffer: wgpu::Buffer,
    character_color_buffer: wgpu::Buffer,
    character_bg_color_buffer: wgpu::Buffer,
    text_data_bind_group_layout: wgpu::BindGroupLayout,
    text_data_bind_group: wgpu::BindGroup,
    text_font_bind_group_layout: wgpu::BindGroupLayout,
    text_font_bind_groups: Vec<wgpu::BindGroup>,
    _text_global_bind_group_layout: wgpu::BindGroupLayout,
    text_global_bind_group: wgpu::BindGroup,
    text_vertex_capacity: usize,
    text_index_capacity: usize,
    text_instance_capacity: usize,
    character_color_capacity: usize,
    character_bg_color_capacity: usize,
    prev_text_instance_data: Vec<TextInstanceData>,
    text_slots: std::collections::HashMap<u32, TextSlotEntry>,
    text_vertex_slab: SlabAllocator,
    text_index_slab: SlabAllocator,
    char_color_slab: SlabAllocator,
    prev_character_colors: Vec<[f32; 4]>,
    prev_character_bg_colors: Vec<[f32; 4]>,
    text_draw_calls: Vec<TextDrawCall>,
    layer_draw_groups: Vec<LayerDrawGroup>,

    cached_atlas_view: Option<wgpu::TextureView>,
    screen_width: f32,
    screen_height: f32,
}

impl PassNode<World> for UiPass {
    fn name(&self) -> &'static str {
        "ui_pass"
    }

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

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

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

    fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, world: &World) {
        self.prepare_pass(device, queue, world);
    }

    fn runs_in_compose_only_phase(&self) -> bool {
        true
    }

    fn execute<'r, 'e>(
        &mut self,
        context: PassExecutionContext<'r, 'e, World>,
    ) -> Result<
        Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
        crate::render::wgpu::rendergraph::RenderGraphError,
    > {
        self.execute_pass(context)
    }
}