use crate::ecs::camera::queries::query_active_camera_matrices;
use crate::ecs::sdf::brick_map::{ATLAS_SIZE, BRICK_CORNER_COUNT, BRICK_SIZE, GRID_SIZE};
use crate::ecs::sdf::clipmap::DEFAULT_LEVEL_COUNT;
use crate::ecs::sdf::resources::SdfMaterial;
use crate::ecs::world::World;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use nalgebra_glm::{Mat4, Vec3};
use wgpu::util::DeviceExt;
use super::sdf_compute::{SdfComputePipelines, prepare_dirty_brick_metadata};
const MAX_MATERIALS: usize = 256;
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct SdfUniforms {
view_projection: [[f32; 4]; 4],
inverse_view_projection: [[f32; 4]; 4],
camera_position: [f32; 4],
clipmap_center: [f32; 4],
level_voxel_sizes: [[f32; 4]; 3],
level_origins: [[[f32; 4]; 2]; 5],
edits_bounds_min: [f32; 4],
edits_bounds_max: [f32; 4],
level_count: u32,
grid_size: u32,
brick_size: u32,
atlas_size: u32,
screen_width: f32,
screen_height: f32,
_pad_sun: [f32; 2],
sun_direction: [f32; 4],
sun_color: [f32; 4],
ambient_color: [f32; 4],
debug_brick_coloring: u32,
terrain_enabled: u32,
terrain_base_height: f32,
terrain_seed: u32,
terrain_octaves: u32,
terrain_frequency: f32,
terrain_amplitude: f32,
terrain_gain: f32,
terrain_max_extent: f32,
terrain_material_id: u32,
_padding0: u32,
_padding1: u32,
_padding2: u32,
_padding3: u32,
_padding4: u32,
_padding5: u32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuSdfMaterial {
base_color: [f32; 4],
roughness_metallic_emissive: [f32; 4],
}
impl From<&SdfMaterial> for GpuSdfMaterial {
fn from(material: &SdfMaterial) -> Self {
Self {
base_color: [
material.base_color.x,
material.base_color.y,
material.base_color.z,
material.emissive.z,
],
roughness_metallic_emissive: [
material.roughness,
material.metallic,
material.emissive.x,
material.emissive.y,
],
}
}
}
pub struct SdfPass {
uniforms_buffer: wgpu::Buffer,
brick_pointers_buffer: wgpu::Buffer,
_brick_atlas: wgpu::Texture,
brick_atlas_view: wgpu::TextureView,
brick_atlas_storage_view: wgpu::TextureView,
_material_atlas: wgpu::Texture,
material_atlas_view: wgpu::TextureView,
material_atlas_storage_view: wgpu::TextureView,
atlas_sampler: wgpu::Sampler,
materials_buffer: wgpu::Buffer,
bind_group_layout: wgpu::BindGroupLayout,
bind_group: Option<wgpu::BindGroup>,
render_pipeline: wgpu::RenderPipeline,
level_count: usize,
compute: SdfComputePipelines,
compute_bind_group_created: bool,
}
impl SdfPass {
pub fn new(device: &wgpu::Device, color_format: wgpu::TextureFormat) -> Self {
let level_count = DEFAULT_LEVEL_COUNT;
let uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("SDF Uniforms Buffer"),
size: std::mem::size_of::<SdfUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let pointers_per_level = GRID_SIZE * GRID_SIZE * GRID_SIZE;
let total_pointers = pointers_per_level * level_count;
let initial_data = vec![-1i32; total_pointers];
let brick_pointers_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("SDF Brick Pointers"),
contents: bytemuck::cast_slice(&initial_data),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
let brick_atlas = device.create_texture(&wgpu::TextureDescriptor {
label: Some("SDF Brick Atlas"),
size: wgpu::Extent3d {
width: ATLAS_SIZE as u32,
height: ATLAS_SIZE as u32,
depth_or_array_layers: ATLAS_SIZE as u32,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D3,
format: wgpu::TextureFormat::R32Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::STORAGE_BINDING,
view_formats: &[],
});
let brick_atlas_view = brick_atlas.create_view(&wgpu::TextureViewDescriptor::default());
let brick_atlas_storage_view =
brick_atlas.create_view(&wgpu::TextureViewDescriptor::default());
let bricks_per_dim = ATLAS_SIZE / BRICK_CORNER_COUNT;
let material_atlas_size = bricks_per_dim * BRICK_SIZE;
let material_atlas = device.create_texture(&wgpu::TextureDescriptor {
label: Some("SDF Material Atlas"),
size: wgpu::Extent3d {
width: material_atlas_size as u32,
height: material_atlas_size as u32,
depth_or_array_layers: material_atlas_size as u32,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D3,
format: wgpu::TextureFormat::R32Uint,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::STORAGE_BINDING,
view_formats: &[],
});
let material_atlas_view =
material_atlas.create_view(&wgpu::TextureViewDescriptor::default());
let material_atlas_storage_view =
material_atlas.create_view(&wgpu::TextureViewDescriptor::default());
let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("SDF Atlas Sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let materials_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("SDF Materials Buffer"),
size: (std::mem::size_of::<GpuSdfMaterial>() * MAX_MATERIALS) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("SDF Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D3,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 5,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Uint,
view_dimension: wgpu::TextureViewDimension::D3,
multisampled: false,
},
count: None,
},
],
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("SDF Render Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("../../shaders/sdf_render.wgsl").into()),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("SDF Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("SDF Render Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::GreaterEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let compute = SdfComputePipelines::new(device);
Self {
uniforms_buffer,
brick_pointers_buffer,
_brick_atlas: brick_atlas,
brick_atlas_view,
brick_atlas_storage_view,
_material_atlas: material_atlas,
material_atlas_view,
material_atlas_storage_view,
atlas_sampler,
materials_buffer,
bind_group_layout,
bind_group: None,
render_pipeline,
level_count,
compute,
compute_bind_group_created: false,
}
}
fn update_brick_pointers(&self, queue: &wgpu::Queue, level: usize, pointers: &[i32]) {
if level < self.level_count {
let pointers_per_level = GRID_SIZE * GRID_SIZE * GRID_SIZE;
let offset = (level * pointers_per_level * std::mem::size_of::<i32>()) as u64;
queue.write_buffer(
&self.brick_pointers_buffer,
offset,
bytemuck::cast_slice(pointers),
);
}
}
}
impl PassNode<World> for SdfPass {
fn name(&self) -> &str {
"sdf_pass"
}
fn reads(&self) -> Vec<&str> {
vec!["depth"]
}
fn writes(&self) -> Vec<&str> {
vec![]
}
fn reads_writes(&self) -> Vec<&str> {
vec!["color"]
}
fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, world: &World) {
let sdf_world = &world.resources.sdf_world;
let sdf_materials = &world.resources.sdf_materials;
if sdf_world.edits.is_empty() && !sdf_world.terrain.enabled {
return;
}
for (level_index, level) in sdf_world.clipmap.levels.iter().enumerate() {
if level.pointer_dirty {
self.update_brick_pointers(queue, level_index, &level.pointers);
}
}
self.compute.reset_frame();
if !sdf_world.pending_gpu_dispatches.is_empty() {
let dirty_bricks: Vec<_> = sdf_world
.pending_gpu_dispatches
.iter()
.map(|dispatch| {
prepare_dirty_brick_metadata(
dispatch.atlas_slot,
dispatch.world_origin,
dispatch.voxel_size,
)
})
.collect();
self.compute.prepare(
queue,
&sdf_world.edits,
&dirty_bricks,
&sdf_world.terrain,
sdf_world.smoothness_scale,
);
if !self.compute_bind_group_created {
self.compute.create_bind_group(
device,
&self.brick_atlas_storage_view,
&self.material_atlas_storage_view,
);
self.compute_bind_group_created = true;
}
}
let gpu_materials: Vec<GpuSdfMaterial> = sdf_materials
.materials
.iter()
.map(GpuSdfMaterial::from)
.collect();
if !gpu_materials.is_empty() {
queue.write_buffer(
&self.materials_buffer,
0,
bytemuck::cast_slice(&gpu_materials),
);
}
let (view, projection, camera_position) =
if let Some(matrices) = query_active_camera_matrices(world) {
let view_inverse = nalgebra_glm::inverse(&matrices.view);
let pos = Vec3::new(
view_inverse[(0, 3)],
view_inverse[(1, 3)],
view_inverse[(2, 3)],
);
(matrices.view, matrices.projection, pos)
} else {
(Mat4::identity(), Mat4::identity(), Vec3::zeros())
};
let view_projection = projection * view;
let inverse_view_projection = nalgebra_glm::inverse(&view_projection);
let voxel_sizes = sdf_world.clipmap.voxel_sizes();
let mut level_voxel_sizes = [[0.0f32; 4]; 3];
for (size_index, &size) in voxel_sizes.iter().enumerate().take(12) {
level_voxel_sizes[size_index / 4][size_index % 4] = size;
}
let mut level_origins = [[[0.0f32; 4]; 2]; 5];
for (level_index, level) in sdf_world.clipmap.levels.iter().enumerate().take(10) {
let row_index = level_index / 2;
let col_index = level_index % 2;
let origin = level.origin_brick;
level_origins[row_index][col_index] =
[origin.x as f32, origin.y as f32, origin.z as f32, 0.0];
}
let sun_direction = Vec3::new(0.5, 0.8, 0.3);
let sun_color = Vec3::new(1.0, 0.95, 0.8);
let (screen_width, screen_height) = world
.resources
.window
.cached_viewport_size
.unwrap_or((1920, 1080));
let mut edits_bounds_min = [f32::MAX, f32::MAX, f32::MAX, 0.0];
let mut edits_bounds_max = [f32::MIN, f32::MIN, f32::MIN, 0.0];
for edit in &sdf_world.edits {
let bounds = edit.effective_bounds();
edits_bounds_min[0] = edits_bounds_min[0].min(bounds.min.x);
edits_bounds_min[1] = edits_bounds_min[1].min(bounds.min.y);
edits_bounds_min[2] = edits_bounds_min[2].min(bounds.min.z);
edits_bounds_max[0] = edits_bounds_max[0].max(bounds.max.x);
edits_bounds_max[1] = edits_bounds_max[1].max(bounds.max.y);
edits_bounds_max[2] = edits_bounds_max[2].max(bounds.max.z);
}
if sdf_world.edits.is_empty() {
edits_bounds_min = [1.0, 1.0, 1.0, 0.0];
edits_bounds_max = [-1.0, -1.0, -1.0, 0.0];
}
let uniforms = SdfUniforms {
view_projection: view_projection.into(),
inverse_view_projection: inverse_view_projection.into(),
camera_position: [camera_position.x, camera_position.y, camera_position.z, 1.0],
clipmap_center: [
sdf_world.clipmap.center.x,
sdf_world.clipmap.center.y,
sdf_world.clipmap.center.z,
0.0,
],
level_voxel_sizes,
level_origins,
edits_bounds_min,
edits_bounds_max,
level_count: sdf_world.clipmap.level_count() as u32,
grid_size: GRID_SIZE as u32,
brick_size: BRICK_SIZE as u32,
atlas_size: ATLAS_SIZE as u32,
screen_width: screen_width as f32,
screen_height: screen_height as f32,
_pad_sun: [0.0; 2],
sun_direction: [sun_direction.x, sun_direction.y, sun_direction.z, 0.0],
sun_color: [sun_color.x, sun_color.y, sun_color.z, 1.0],
ambient_color: [0.1, 0.1, 0.15, 1.0],
debug_brick_coloring: if sdf_world.debug_brick_coloring { 1 } else { 0 },
terrain_enabled: if sdf_world.terrain.enabled { 1 } else { 0 },
terrain_base_height: sdf_world.terrain.base_height,
terrain_seed: sdf_world.terrain.seed,
terrain_octaves: sdf_world.terrain.octaves,
terrain_frequency: sdf_world.terrain.frequency,
terrain_amplitude: sdf_world.terrain.amplitude,
terrain_gain: sdf_world.terrain.gain,
terrain_max_extent: sdf_world.terrain.max_surface_extent(),
terrain_material_id: sdf_world.terrain.material_id,
_padding0: 0,
_padding1: 0,
_padding2: 0,
_padding3: 0,
_padding4: 0,
_padding5: 0,
};
queue.write_buffer(&self.uniforms_buffer, 0, bytemuck::cast_slice(&[uniforms]));
}
fn execute<'r, 'e>(
&mut self,
context: PassExecutionContext<'r, 'e, World>,
) -> crate::render::wgpu::rendergraph::Result<
Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
> {
let sdf_world = &context.configs.resources.sdf_world;
if sdf_world.edits.is_empty() && !sdf_world.terrain.enabled {
return Ok(context.into_sub_graph_commands());
}
if self.compute.has_pending_work() {
self.compute.dispatch(context.encoder);
}
if self.bind_group.is_none() {
self.bind_group = Some(
context
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("SDF Bind Group"),
layout: &self.bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.uniforms_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: self.brick_pointers_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(
&self.brick_atlas_view,
),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&self.atlas_sampler),
},
wgpu::BindGroupEntry {
binding: 4,
resource: self.materials_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(
&self.material_atlas_view,
),
},
],
}),
);
}
let bind_group = self.bind_group.as_ref().unwrap();
let (color_view, color_load, color_store) = context.get_color_attachment("color")?;
let (depth_view, depth_load, depth_store) = context.get_depth_attachment("depth")?;
{
let mut render_pass = context
.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("SDF Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: color_view,
resolve_target: None,
ops: wgpu::Operations {
load: color_load,
store: color_store,
},
depth_slice: None,
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: depth_view,
depth_ops: Some(wgpu::Operations {
load: depth_load,
store: depth_store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
});
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(0, bind_group, &[]);
render_pass.draw(0..6, 0..1);
}
Ok(context.into_sub_graph_commands())
}
}