summaryrefslogtreecommitdiff
path: root/src/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'src/renderer')
-rw-r--r--src/renderer/mod.rs537
1 files changed, 242 insertions, 295 deletions
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 1cf39e2..39a507b 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -13,20 +13,29 @@ use crate::{
sim::{cell::materials::MaterialId, cell_sim::world::World, rb_sim::RbSimManager},
};
-struct RendererChunk {
- texture: wgpu::Texture,
- bind_group: wgpu::BindGroup,
-}
-
-struct RendererRbEntity {
- texture: wgpu::Texture,
- bind_group: wgpu::BindGroup,
-}
+// TODO derive from shared chunk config
+const CHUNK_SLOTS: usize = 11 * 200;
+const RB_ENTITY_SLOTS: usize = 64;
+const CELL_SLOTS: usize = CHUNK_SLOTS + RB_ENTITY_SLOTS;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
-struct ChunkData {
- origin: [i32; 2],
+struct Instance {
+ centre: [f32; 2],
+ cos_sin: [f32; 2],
+ half_size: [f32; 2],
+ dims: [u32; 2],
+ cell_offset: u32,
+ _padding: u32,
+}
+
+fn srgb_to_linear(channel: u8) -> f32 {
+ let channel = channel as f32 / 255.0;
+ if channel <= 0.04045 {
+ channel / 12.92
+ } else {
+ ((channel + 0.055) / 1.055).powf(2.4)
+ }
}
pub struct RendererState {
@@ -44,12 +53,13 @@ pub struct RendererState {
egui_renderer: egui_wgpu::Renderer,
// world pixels
- renderer_chunks: Vec<RendererChunk>,
pixels_pipeline: wgpu::RenderPipeline,
camera_uniform_buffer: wgpu::Buffer,
- camera_uniform_bind_group: wgpu::BindGroup,
+ instance_buffer: wgpu::Buffer,
+ cell_buffer: wgpu::Buffer,
+ pixels_bind_group: wgpu::BindGroup,
- renderer_rb_entities: FxHashMap<u32, RendererRbEntity>,
+ renderer_rb_entities: FxHashMap<u32, usize>,
}
impl RendererState {
@@ -123,35 +133,57 @@ impl RendererState {
}
});
- let camera_uniform_bind_group_layout =
+ let pixels_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
- entries: &[wgpu::BindGroupLayoutEntry {
- ty: wgpu::BindingType::Buffer {
- ty: wgpu::BufferBindingType::Uniform,
- has_dynamic_offset: false,
- min_binding_size: None,
+ entries: &[
+ // camera uniform
+ wgpu::BindGroupLayoutEntry {
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: None,
+ },
+ binding: 0,
+ count: None,
+ visibility: wgpu::ShaderStages::VERTEX,
},
- binding: 0,
- count: None,
- visibility: wgpu::ShaderStages::VERTEX,
- }],
+ // palette
+ wgpu::BindGroupLayoutEntry {
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Storage { read_only: true },
+ has_dynamic_offset: false,
+ min_binding_size: None,
+ },
+ binding: 1,
+ count: None,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ },
+ // instance buffer
+ wgpu::BindGroupLayoutEntry {
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Storage { read_only: true },
+ has_dynamic_offset: false,
+ min_binding_size: None,
+ },
+ binding: 2,
+ count: None,
+ visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
+ },
+ // cells
+ wgpu::BindGroupLayoutEntry {
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Storage { read_only: true },
+ has_dynamic_offset: false,
+ min_binding_size: None,
+ },
+ binding: 3,
+ count: None,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ },
+ ],
});
- let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
- label: None,
- entries: &[wgpu::BindGroupLayoutEntry {
- ty: wgpu::BindingType::Texture {
- sample_type: wgpu::TextureSampleType::Float { filterable: true },
- view_dimension: wgpu::TextureViewDimension::D2,
- multisampled: false,
- },
- binding: 0,
- count: None,
- visibility: wgpu::ShaderStages::FRAGMENT,
- }],
- });
-
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("../shader/shader.wgsl").into()),
@@ -160,11 +192,8 @@ impl RendererState {
let pixels_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
- immediate_size: 16,
- bind_group_layouts: &[
- Some(&camera_uniform_bind_group_layout),
- Some(&bind_group_layout),
- ],
+ immediate_size: 0,
+ bind_group_layouts: &[Some(&pixels_bind_group_layout)],
});
let pixels_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@@ -181,7 +210,7 @@ impl RendererState {
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
- blend: Some(wgpu::BlendState::REPLACE),
+ blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
@@ -208,59 +237,61 @@ impl RendererState {
mapped_at_creation: false,
});
- let camera_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: None,
- layout: &camera_uniform_bind_group_layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: camera_uniform_buffer.as_entire_binding(),
- }],
+ let mut palette = [0.0f32; MaterialId::ALL.len() * 4];
+ for material in MaterialId::ALL {
+ let def = material.def();
+ let i = material as usize * 4;
+ palette[i] = srgb_to_linear(def.color.0);
+ palette[i + 1] = srgb_to_linear(def.color.1);
+ palette[i + 2] = srgb_to_linear(def.color.2);
+ palette[i + 3] = def.color.3 as f32 / 255.0;
+ }
+
+ let palette_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("Palette buffer"),
+ size: size_of_val(&palette) as u64,
+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
});
- let mut renderer_chunks: Vec<RendererChunk> = Vec::new();
- // match number of world chunks
- // TODO refactor so that this implicit
- for _ in -10..1 {
- for _ in -100..100 {
- let texture = device.create_texture(&wgpu::TextureDescriptor {
- label: None,
- mip_level_count: 1,
- sample_count: 1,
- usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
- format: wgpu_types::TextureFormat::Rgba8UnormSrgb,
- size: wgpu::Extent3d {
- width: CHUNK_SIZE as u32,
- height: CHUNK_SIZE as u32,
- depth_or_array_layers: 1,
- },
- dimension: wgpu::TextureDimension::D2,
- view_formats: &[],
- });
+ queue.write_buffer(&palette_buffer, 0, bytemuck::cast_slice(&palette));
- let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: None,
- layout: &bind_group_layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::TextureView(&texture.create_view(
- &wgpu::TextureViewDescriptor {
- dimension: Some(wgpu::TextureViewDimension::D2),
- usage: Some(
- wgpu::TextureUsages::TEXTURE_BINDING
- | wgpu::TextureUsages::COPY_DST,
- ),
- ..wgpu::TextureViewDescriptor::default()
- },
- )),
- }],
- });
+ let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("Instance buffer"),
+ size: (CELL_SLOTS * size_of::<Instance>()) as u64,
+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
- renderer_chunks.push(RendererChunk {
- texture,
- bind_group,
- })
- }
- }
+ let cell_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("Cell buffer"),
+ size: (CELL_SLOTS * CELLS_IN_CHUNK) as u64,
+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+
+ let pixels_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: None,
+ layout: &pixels_bind_group_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: camera_uniform_buffer.as_entire_binding(),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: palette_buffer.as_entire_binding(),
+ },
+ wgpu::BindGroupEntry {
+ binding: 2,
+ resource: instance_buffer.as_entire_binding(),
+ },
+ wgpu::BindGroupEntry {
+ binding: 3,
+ resource: cell_buffer.as_entire_binding(),
+ },
+ ],
+ });
RendererState {
window,
@@ -274,10 +305,11 @@ impl RendererState {
egui_state,
egui_renderer,
- renderer_chunks,
pixels_pipeline,
- camera_uniform_bind_group,
camera_uniform_buffer,
+ instance_buffer,
+ cell_buffer,
+ pixels_bind_group,
renderer_rb_entities: FxHashMap::default(),
}
@@ -386,11 +418,11 @@ impl RendererState {
bytemuck::bytes_of(&camera.to_uniform()),
);
- // write the chunk textures
- let mut chunk_buffer: [u8; CELLS_IN_CHUNK * 4] = [0; (CELLS_IN_CHUNK * 4)];
+ let mut cell_buffer: [u8; CELLS_IN_CHUNK] = [0; CELLS_IN_CHUNK];
+ let mut instances: Vec<Instance> = Vec::new();
{
- puffin::profile_scope!("Upload chunk textures");
+ puffin::profile_scope!("Upload chunk cells");
for &idx in world.chunk_position_to_chunk_idx.values() {
let chunk = &mut world.chunks[idx];
if !chunk.needs_texture_update {
@@ -398,227 +430,142 @@ impl RendererState {
}
chunk.needs_texture_update = false;
- // TODO use material palette to improve bandwidth of upload
- for i in 0..CELLS_IN_CHUNK {
- let material = chunk.cells[i].material.def();
- chunk_buffer[i * 4] = material.color.0;
- chunk_buffer[i * 4 + 1] = material.color.1;
- chunk_buffer[i * 4 + 2] = material.color.2;
- chunk_buffer[i * 4 + 3] = material.color.3;
+ for (byte, cell) in cell_buffer.iter_mut().zip(chunk.cells.iter()) {
+ *byte = cell.material as u8;
}
- let render_chunk = &self.renderer_chunks[idx];
- self.queue.write_texture(
- wgpu::TexelCopyTextureInfo {
- texture: &render_chunk.texture,
- aspect: wgpu::TextureAspect::All,
- mip_level: 0,
- origin: wgpu::Origin3d::ZERO,
- },
- &chunk_buffer,
- wgpu::TexelCopyBufferLayout {
- bytes_per_row: Some(CHUNK_SIZE as u32 * 4),
- offset: 0,
- rows_per_image: Some(CHUNK_SIZE as u32),
- },
- wgpu::Extent3d {
- width: CHUNK_SIZE as u32,
- height: CHUNK_SIZE as u32,
- depth_or_array_layers: 1,
- },
+ self.queue.write_buffer(
+ &self.cell_buffer,
+ (idx * CELLS_IN_CHUNK) as u64,
+ &cell_buffer,
);
}
}
{
- puffin::profile_scope!("Upload entity textures");
+ puffin::profile_scope!("Upload entity cells");
for entity in rb_sim_manager.rb_entities.values() {
// TODO add "needs texture update"?
-
- // TODO use material palette to improve bandwidth of upload
- for i in 0..CELLS_IN_CHUNK {
- let material = entity.cells[i].material.def();
- chunk_buffer[i * 4] = material.color.0;
- chunk_buffer[i * 4 + 1] = material.color.1;
- chunk_buffer[i * 4 + 2] = material.color.2;
- chunk_buffer[i * 4 + 3] = material.color.3;
+ for (byte, cell) in cell_buffer.iter_mut().zip(entity.cells.iter()) {
+ *byte = cell.material as u8;
}
- let render_entity = &self.renderer_rb_entities.entry(entity.id).or_insert({
- let texture = self.device.create_texture(&wgpu::TextureDescriptor {
- label: None,
- mip_level_count: 1,
- sample_count: 1,
- usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
- format: wgpu_types::TextureFormat::Rgba8UnormSrgb,
- size: wgpu::Extent3d {
- width: CHUNK_SIZE as u32,
- height: CHUNK_SIZE as u32,
- depth_or_array_layers: 1,
- },
- dimension: wgpu::TextureDimension::D2,
- view_formats: &[],
- });
-
- let bind_group_layout =
- self.device
- .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
- label: None,
- entries: &[wgpu::BindGroupLayoutEntry {
- ty: wgpu::BindingType::Texture {
- sample_type: wgpu::TextureSampleType::Float {
- filterable: true,
- },
- view_dimension: wgpu::TextureViewDimension::D2,
- multisampled: false,
- },
- binding: 0,
- count: None,
- visibility: wgpu::ShaderStages::FRAGMENT,
- }],
- });
+ let next_slot = CHUNK_SLOTS + self.renderer_rb_entities.len();
+ let slot = *self
+ .renderer_rb_entities
+ .entry(entity.id)
+ .or_insert(next_slot);
- let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: None,
- layout: &bind_group_layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::TextureView(&texture.create_view(
- &wgpu::TextureViewDescriptor {
- dimension: Some(wgpu::TextureViewDimension::D2),
- usage: Some(
- wgpu::TextureUsages::TEXTURE_BINDING
- | wgpu::TextureUsages::COPY_DST,
- ),
- ..wgpu::TextureViewDescriptor::default()
- },
- )),
- }],
- });
-
- RendererRbEntity {
- texture,
- bind_group,
- }
- });
-
- self.queue.write_texture(
- wgpu::TexelCopyTextureInfo {
- texture: &render_entity.texture,
- aspect: wgpu::TextureAspect::All,
- mip_level: 0,
- origin: wgpu::Origin3d::ZERO,
- },
- &chunk_buffer,
- wgpu::TexelCopyBufferLayout {
- bytes_per_row: Some(CHUNK_SIZE as u32 * 4),
- offset: 0,
- rows_per_image: Some(CHUNK_SIZE as u32),
- },
- wgpu::Extent3d {
- width: CHUNK_SIZE as u32,
- height: CHUNK_SIZE as u32,
- depth_or_array_layers: 1,
- },
+ self.queue.write_buffer(
+ &self.cell_buffer,
+ (slot * CELLS_IN_CHUNK) as u64,
+ &cell_buffer,
);
}
+ }
- {
- puffin::profile_scope!("Main render pass");
- let mut render_pass: wgpu::RenderPass<'_> =
- encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("Render Pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: &view,
- resolve_target: None,
- depth_slice: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Clear(wgpu::Color {
- r: 0.0,
- g: 0.0,
- b: 0.0,
- a: 1.0,
- }),
- store: wgpu::StoreOp::Store,
- },
- })],
- depth_stencil_attachment: None,
- occlusion_query_set: None,
- timestamp_writes: None,
- multiview_mask: None,
- });
-
- render_pass.set_pipeline(&self.pixels_pipeline);
-
- render_pass.set_bind_group(0, &self.camera_uniform_bind_group, &[]);
+ {
+ puffin::profile_scope!("Build instances");
+ let (xl, xu, yl, yu) = camera.viewport_bounds_world();
+ let ((cxl, cyl), _) = World::split_game_position(xl.floor() as i32, yl.floor() as i32);
+ let ((cxu, cyu), _) = World::split_game_position(xu.floor() as i32, yu.floor() as i32);
- let (xl, xu, yl, yu) = camera.viewport_bounds_world();
- let ((cxl, cyl), _) =
- World::split_game_position(xl.floor() as i32, yl.floor() as i32);
- let ((cxu, cyu), _) =
- World::split_game_position(xu.floor() as i32, yu.floor() as i32);
+ let half_size = [CHUNK_SIZE as f32 / 2.0, CHUNK_SIZE as f32 / 2.0];
+ let dims = [CHUNK_SIZE as u32, CHUNK_SIZE as u32];
- for cx in cxl..=cxu {
- for cy in cyl..=cyu {
- if let Some(idx) = world.chunk_position_to_chunk_idx.get(&(cx, cy)) {
- let render_chunk = &self.renderer_chunks[*idx];
- render_pass.set_bind_group(1, &render_chunk.bind_group, &[]);
- render_pass.set_immediates(
- 0,
- bytemuck::bytes_of(&ChunkData {
- origin: [cx * CHUNK_SIZE, cy * CHUNK_SIZE],
- }),
- );
- render_pass.draw(0..4, 0..1);
- }
+ for cx in cxl..=cxu {
+ for cy in cyl..=cyu {
+ if let Some(idx) = world.chunk_position_to_chunk_idx.get(&(cx, cy)) {
+ instances.push(Instance {
+ centre: [
+ (cx * CHUNK_SIZE) as f32 + half_size[0],
+ (cy * CHUNK_SIZE) as f32 + half_size[1],
+ ],
+ cos_sin: [1.0, 0.0],
+ half_size,
+ dims,
+ cell_offset: (idx * CELLS_IN_CHUNK) as u32,
+ _padding: 0,
+ });
}
}
+ }
- // TODO this is inefficient
- for rb_entity in rb_sim_manager.rb_entities.keys() {
- let renderer_rb_entity = self.renderer_rb_entities.get(rb_entity).unwrap();
- let (x, y) = rb_sim_manager.get_rb_entity_position(*rb_entity).unwrap();
- render_pass.set_bind_group(1, &renderer_rb_entity.bind_group, &[]);
- render_pass.set_immediates(
- 0,
- bytemuck::bytes_of(&ChunkData {
- origin: [x.round() as i32, y.round() as i32],
- }),
- );
- render_pass.draw(0..4, 0..1);
- }
+ for (id, slot) in &self.renderer_rb_entities {
+ let (x, y, cos, sin) = rb_sim_manager.get_rb_entity_transform(*id).unwrap();
+ instances.push(Instance {
+ centre: [x, y],
+ cos_sin: [cos, sin],
+ half_size,
+ dims,
+ cell_offset: (slot * CELLS_IN_CHUNK) as u32,
+ _padding: 0,
+ });
}
- {
- puffin::profile_scope!("Egui render pass");
- let mut egui_pass = encoder
- .begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("egui pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: &view,
- resolve_target: None,
- depth_slice: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: wgpu::StoreOp::Store,
- },
- })],
- depth_stencil_attachment: None,
- timestamp_writes: None,
- occlusion_query_set: None,
- multiview_mask: None,
- })
- .forget_lifetime();
+ self.queue
+ .write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&instances));
+ }
- self.egui_renderer
- .render(&mut egui_pass, &clipped_primitives, &screen_descriptor);
- }
+ {
+ puffin::profile_scope!("Main render pass");
+ let mut render_pass: wgpu::RenderPass<'_> =
+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("Render Pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: &view,
+ resolve_target: None,
+ depth_slice: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Clear(wgpu::Color {
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 1.0,
+ }),
+ store: wgpu::StoreOp::Store,
+ },
+ })],
+ depth_stencil_attachment: None,
+ occlusion_query_set: None,
+ timestamp_writes: None,
+ multiview_mask: None,
+ });
- {
- puffin::profile_scope!("Submit queue and present");
- self.queue.submit(std::iter::once(encoder.finish()));
- output.present();
- }
+ render_pass.set_pipeline(&self.pixels_pipeline);
+ render_pass.set_bind_group(0, &self.pixels_bind_group, &[]);
+ render_pass.draw(0..4, 0..instances.len() as u32);
+ }
+
+ {
+ puffin::profile_scope!("Egui render pass");
+ let mut egui_pass = encoder
+ .begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("egui pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: &view,
+ resolve_target: None,
+ depth_slice: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: wgpu::StoreOp::Store,
+ },
+ })],
+ depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
+ multiview_mask: None,
+ })
+ .forget_lifetime();
+
+ self.egui_renderer
+ .render(&mut egui_pass, &clipped_primitives, &screen_descriptor);
+ }
+
+ {
+ puffin::profile_scope!("Submit queue and present");
+ self.queue.submit(std::iter::once(encoder.finish()));
+ output.present();
}
}
}