roast2d_internal 0.3.0-alpha.1

Roast2D internal crate
Documentation
use std::cell::RefCell;

use crate::engine::Engine;
use crate::prelude::renderer_types::{TextureResource, Vertex};
use crate::prelude::wgpu::util::DeviceExt;
use crate::prelude::wgpu::{self, Device, Queue, RenderPass};

use crate::render::BackendState;

use super::traits::PostRenderer;

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct CRTSettings {
    pub inv_screen_scale: [f32; 2],
    pub phosphor: f32,
    pub vscanlines: f32,
    pub input_gamma: f32,
    pub output_gamma: f32,
    pub sharpness: f32,
    pub color_boost: f32,
    pub red_boost: f32,
    pub green_boost: f32,
    pub blue_boost: f32,
    pub scanlines_strength: f32,
    pub beam_min_width: f32,
    pub beam_max_width: f32,
    pub crt_anti_ringing: f32,
    // padding align to 8 bytes
    pub need_refresh: f32,
}

impl Default for CRTSettings {
    fn default() -> Self {
        Self {
            inv_screen_scale: [1.0, 1.0],
            phosphor: 1.0,
            vscanlines: 0.0,
            input_gamma: 2.5,
            output_gamma: 2.2,
            sharpness: 1.0,
            color_boost: 1.5,
            red_boost: 1.0,
            green_boost: 1.0,
            blue_boost: 1.0,
            scanlines_strength: 0.5,
            beam_min_width: 0.86,
            beam_max_width: 1.0,
            crt_anti_ringing: 0.8,
            need_refresh: 1.0,
        }
    }
}

struct State {
    device: Device,
    queue: Queue,
    pipeline: wgpu::RenderPipeline,
    bind_group_layout: wgpu::BindGroupLayout,
    vertex_buffer: wgpu::Buffer,
    index_buffer: wgpu::Buffer,
    uniform_buffer: wgpu::Buffer,
}

impl State {
    pub fn new(param: BackendState) -> Self {
        let BackendState {
            device,
            queue,
            surface_config,
        } = param;
        let format = surface_config.format;
        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("post-processing vertex module"),
            source: wgpu::ShaderSource::Wgsl(
                include_str!("../../assets/shaders/crt.wgsl").into(),
            ),
        });
        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: Some("CRT Bind Group Layout"),
            entries: &[
                wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Texture {
                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        view_dimension: wgpu::TextureViewDimension::D2,
                        multisampled: false,
                    },
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 1,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 2,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                },
            ],
        });
        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("post-processing layout"),
            bind_group_layouts: &[&bind_group_layout],
            push_constant_ranges: &[],
        });
        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            cache: None,
            label: Some("post-processing pipeline"),
            layout: Some(&pipeline_layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: Some("vs_main"),
                buffers: &[Vertex::desc()],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: Some("fs_main"),
                targets: &[Some(wgpu::ColorTargetState {
                    format,
                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: Some(wgpu::Face::Back),
                polygon_mode: wgpu::PolygonMode::Fill,
                unclipped_depth: false,
                conservative: false,
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
        });
        let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("crt vertex buffer"),
            contents: bytemuck::cast_slice(&[
                Vertex {
                    pos: [-1.0, -1.0],
                    color: [1.0, 1.0, 1.0, 1.0],
                    tex_coord: [0.0, 1.0],
                },
                Vertex {
                    pos: [1.0, -1.0],
                    color: [1.0, 1.0, 1.0, 1.0],
                    tex_coord: [1.0, 1.0],
                },
                Vertex {
                    pos: [1.0, 1.0],
                    color: [1.0, 1.0, 1.0, 1.0],
                    tex_coord: [1.0, 0.0],
                },
                Vertex {
                    pos: [-1.0, 1.0],
                    color: [1.0, 1.0, 1.0, 1.0],
                    tex_coord: [0.0, 0.0],
                },
            ]),
            usage: wgpu::BufferUsages::VERTEX,
        });
        let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("crt index buffer"),
            contents: bytemuck::cast_slice(&[0u16, 1, 2, 0, 2, 3]),
            usage: wgpu::BufferUsages::INDEX,
        });
        let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("CRT Settings Buffer"),
            size: std::mem::size_of::<CRTSettings>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });
        Self {
            device,
            queue,
            pipeline,
            bind_group_layout,
            vertex_buffer,
            index_buffer,
            uniform_buffer,
        }
    }
}

pub struct CrtRenderer {
    state: State,
    settings: RefCell<CRTSettings>,
}

impl CrtRenderer {
    pub fn setup(settings: CRTSettings, param: BackendState) -> Self {
        let state = State::new(param);
        Self {
            state,
            settings: RefCell::new(settings),
        }
    }
}

impl PostRenderer for CrtRenderer {
    fn resize(&self, g: &Engine) {
        let mut settings = self.settings.borrow_mut();
        settings.inv_screen_scale = g.render.inv_screen_scale.into();
        settings.need_refresh = 1.0;
    }

    fn draw(&self, texture: &TextureResource, pass: &mut RenderPass) {
        let bind_group = self
            .state
            .device
            .create_bind_group(&wgpu::BindGroupDescriptor {
                layout: &self.state.bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(&texture.view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(&texture.sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: self.state.uniform_buffer.as_entire_binding(),
                    },
                ],
                label: Some("CRT bind group"),
            });
        pass.set_pipeline(&self.state.pipeline);
        pass.set_bind_group(0, &bind_group, &[]);
        pass.set_vertex_buffer(0, self.state.vertex_buffer.slice(..));
        pass.set_index_buffer(self.state.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
        pass.draw_indexed(0..6, 0, 0..1);
    }

    fn frame_pass(&self, _g: &Engine, _pass: &mut RenderPass) {
        let mut settings = self.settings.borrow_mut();
        if settings.need_refresh > 0.0 {
            settings.need_refresh = 0.0;
            self.state.queue.write_buffer(
                &self.state.uniform_buffer,
                0,
                bytemuck::cast_slice(&[*settings]),
            );
        }
    }

    fn frame_end(&self, _pass: &mut RenderPass) {}
}