284 lines
9.1 KiB
Rust
Executable File
284 lines
9.1 KiB
Rust
Executable File
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);
|
|
}
|
|
}
|
|
}
|