use std::{ops::Range, sync::Arc}; use crate::state::{FrameResources, RenderState}; use bevy_ecs::{ entity::Entity, resource::Resource, system::{Res, ResMut}, }; use bytemuck::{Pod, Zeroable}; use modular_bitfield::{Specifier, bitfield, specifiers::*}; use rangemap::RangeSet; use tracing::error; use voxtech_terrain::chunk::ChunkId; use wgpu::{ naga::FastHashMap, util::{BufferInitDescriptor, DeviceExt, RenderEncoder}, *, }; #[derive(Specifier, Debug, Clone, Copy, PartialEq, Eq, Hash)] #[bits = 3] pub enum FaceDirection { Top, Bottom, Left, Right, Front, Back, } #[bitfield] #[derive(Specifier, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct FaceColour { pub r: u8, pub g: u8, pub b: u8, pub a: u8, } #[repr(C)] #[bitfield] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Zeroable, Pod)] pub struct Face { pub face: FaceDirection, pub x: B5, pub y: B5, pub z: B5, pub w: B5, pub h: B5, pub meta: B4, pub colour: FaceColour, } #[repr(C)] #[derive(Debug, Clone, Copy, Zeroable, Pod)] pub struct Vertex { pub position: [f32; 2], } impl Vertex { pub fn square() -> [Self; 4] { [ Self { position: [0.0, 1.0], }, Self { position: [0.0, 0.0], }, Self { position: [1.0, 1.0], }, Self { position: [1.0, 0.0], }, ] } pub fn pipeline(device: &Device, surface_config: &SurfaceConfiguration) -> RenderPipeline { let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::VERTEX_FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Storage { read_only: true }, has_dynamic_offset: false, min_binding_size: None, }, count: None, }], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let vertex_buffers = [wgpu::VertexBufferLayout { array_stride: size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[wgpu::VertexAttribute { format: wgpu::VertexFormat::Float32x2, offset: 0, shader_location: 0, }], }]; device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("chunk_renderer"), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), compilation_options: Default::default(), buffers: &vertex_buffers, }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), compilation_options: Default::default(), targets: &[ Some(surface_config.view_formats[0].into()), Some(TextureFormat::R32Uint.into()), ], }), primitive: wgpu::PrimitiveState { cull_mode: Some(wgpu::Face::Back), topology: PrimitiveTopology::TriangleStrip, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview_mask: None, cache: None, }) } } #[derive(Resource)] pub struct ChunkRenderer { vertex_buffer: Buffer, face_buffer: Arc, bind_group: BindGroup, pipeline: RenderPipeline, id_texture: Texture, free: RangeSet, used: FastHashMap>, writes: Vec<(Range, Vec)>, } impl ChunkRenderer { pub fn new(device: &Device, surface_config: &SurfaceConfiguration) -> Self { const MAX_FACE_COUNT: usize = 100000; let vertex_data = Vertex::square(); let pipeline = Vertex::pipeline(device, surface_config); let vertex_buffer = device.create_buffer_init(&BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(&vertex_data), usage: BufferUsages::VERTEX, }); let face_buffer = device.create_buffer(&BufferDescriptor { label: Some("Face Buffer"), size: (MAX_FACE_COUNT * size_of::()) as u64, mapped_at_creation: false, usage: BufferUsages::STORAGE | BufferUsages::COPY_DST, }); let bind_group = device.create_bind_group(&BindGroupDescriptor { label: Some("Chunk bindings"), layout: &pipeline.get_bind_group_layout(0), entries: &[BindGroupEntry { binding: 0, resource: BindingResource::Buffer(BufferBinding { buffer: &face_buffer, offset: 0, size: None, }), }], }); Self { vertex_buffer, face_buffer: face_buffer.into(), pipeline, bind_group, id_texture: device.create_texture(&TextureDescriptor { label: Some("Chunk visibility texture"), size: Extent3d { width: surface_config.width, height: surface_config.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, format: TextureFormat::R32Uint, usage: TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }), free: RangeSet::from_iter([0..MAX_FACE_COUNT]), used: FastHashMap::default(), writes: vec![], } } pub fn set_mesh(&mut self, chunk: ChunkId, faces: Vec) -> Range { for range in self.free.iter() { if range.len() >= faces.len() { let mut range = range.clone(); range.end = range.start + faces.len(); self.free.remove(range.clone()); self.used.entry(chunk).insert(range.clone()); let bounds = (range.start * size_of::()) as u64 ..(range.end * size_of::()) as u64; self.writes.push((bounds, faces)); return range; } } error!( "Could not find a suitable buffer range for entity {chunk:?}. The chunk will not be rendered." ); if let Some(range) = self.used.remove(&chunk) { self.free.insert(range); } 0..0 } pub(crate) fn render( mut renderer: ResMut, state: Res, mut frame: ResMut, ) { for (range, faces) in std::mem::take(&mut renderer.writes) { state.queue.write_buffer( &renderer.face_buffer, range.start, bytemuck::cast_slice(&faces), ); } let view = frame.screen_view.clone(); let mut pass = frame .command_encoder .begin_render_pass(&RenderPassDescriptor { label: Some("Render triangle"), color_attachments: &[ Some(RenderPassColorAttachment { view: &view, depth_slice: None, resolve_target: None, ops: Operations { load: LoadOp::DontCare(LoadOpDontCare::default()), store: StoreOp::Store, }, }), Some(RenderPassColorAttachment { view: &renderer .id_texture .create_view(&TextureViewDescriptor::default()), depth_slice: None, resolve_target: None, ops: Operations { load: LoadOp::DontCare(LoadOpDontCare::default()), store: StoreOp::Store, }, }), ], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); pass.set_bind_group(0, &renderer.bind_group, &[]); pass.set_pipeline(&renderer.pipeline); pass.set_vertex_buffer(0, renderer.vertex_buffer.slice(..)); for range in renderer.used.values() { pass.draw(0..4, range.start as u32..range.end as u32); } } }