summaryrefslogtreecommitdiff
path: root/src/renderer
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-16 17:29:42 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-16 17:29:42 -0700
commitf177cc716c5f2aa5b50b14ccbb421de89e3a7854 (patch)
treedb06f9f44251d6d90e5e9709d47e6cc4397b9443 /src/renderer
parent40e9d818195824749293dff3afcfdd5c1432adbe (diff)
wip
Diffstat (limited to 'src/renderer')
-rw-r--r--src/renderer/mod.rs250
-rw-r--r--src/renderer/ui.rs2
2 files changed, 187 insertions, 65 deletions
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 5231bc3..e022fe7 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -2,6 +2,7 @@ mod ui;
use std::sync::Arc;
+use fxhash::FxHashMap;
use winit::window::Window;
use crate::{
@@ -9,7 +10,7 @@ use crate::{
camera::Camera,
config::{CELLS_IN_CHUNK, CHUNK_SIZE},
renderer::ui::draw_egui,
- sim::world::World,
+ sim::{cell_sim::world::World, rb_sim::RbSimManager},
};
struct RendererChunk {
@@ -17,6 +18,11 @@ struct RendererChunk {
bind_group: wgpu::BindGroup,
}
+struct RendererRbEntity {
+ texture: wgpu::Texture,
+ bind_group: wgpu::BindGroup,
+}
+
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct ChunkData {
@@ -42,6 +48,8 @@ pub struct RendererState {
pixels_pipeline: wgpu::RenderPipeline,
camera_uniform_buffer: wgpu::Buffer,
camera_uniform_bind_group: wgpu::BindGroup,
+
+ renderer_rb_entities: FxHashMap<u32, RendererRbEntity>,
}
impl RendererState {
@@ -270,6 +278,8 @@ impl RendererState {
pixels_pipeline,
camera_uniform_bind_group,
camera_uniform_buffer,
+
+ renderer_rb_entities: FxHashMap::default(),
}
}
@@ -285,6 +295,7 @@ impl RendererState {
pub fn render(
&mut self,
world: &mut World,
+ rb_sim_manager: &RbSimManager,
camera: &mut Camera,
config: &mut Config,
diagnostics: &Diagnostics,
@@ -420,81 +431,192 @@ impl RendererState {
}
{
- 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,
+ puffin::profile_scope!("Upload entity textures");
+ 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;
+ }
+
+ 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,
},
- })],
- depth_stencil_attachment: None,
- occlusion_query_set: None,
- timestamp_writes: None,
- multiview_mask: None,
+ 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 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,
+ }
});
- render_pass.set_pipeline(&self.pixels_pipeline);
+ 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,
+ },
+ );
+ }
+
+ {
+ 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_bind_group(0, &self.camera_uniform_bind_group, &[]);
+ render_pass.set_pipeline(&self.pixels_pipeline);
- 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);
+ render_pass.set_bind_group(0, &self.camera_uniform_bind_group, &[]);
- // TODO only visible chunks
- 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, cy] }));
- render_pass.draw(0..4, 0..1);
+ 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);
+
+ 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, cy] }),
+ );
+ render_pass.draw(0..4, 0..1);
+ }
}
}
+
+ // 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);
+ }
}
- }
- {
- 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();
+ {
+ 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);
- }
+ 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();
+ {
+ puffin::profile_scope!("Submit queue and present");
+ self.queue.submit(std::iter::once(encoder.finish()));
+ output.present();
+ }
}
}
}
diff --git a/src/renderer/ui.rs b/src/renderer/ui.rs
index 2e4ebc4..77af59d 100644
--- a/src/renderer/ui.rs
+++ b/src/renderer/ui.rs
@@ -2,7 +2,7 @@ use egui::{Color32, Stroke, Ui, epaint::CircleShape};
use crate::{
Camera, Config, Diagnostics, Input,
- sim::{materials::MaterialId, world::World},
+ sim::{cell::materials::MaterialId, cell_sim::world::World},
};
pub fn draw_egui<'a>(