Files
voxtech/rendering/src/chunk/renderer.rs
T

284 lines
9.1 KiB
Rust
Raw Normal View History

2026-06-04 18:22:57 +02:00
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::<Self>() 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<Buffer>,
bind_group: BindGroup,
pipeline: RenderPipeline,
id_texture: Texture,
free: RangeSet<usize>,
used: FastHashMap<ChunkId, Range<usize>>,
writes: Vec<(Range<u64>, Vec<Face>)>,
}
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::<Face>()) 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<Face>) -> Range<usize> {
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::<Face>()) as u64
..(range.end * size_of::<Face>()) 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<ChunkRenderer>,
state: Res<RenderState>,
mut frame: ResMut<FrameResources>,
) {
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);
}
}
}