#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct ReprojUniform {
inv_prev_vp: [[f32; 4]; 4],
cur_vp: [[f32; 4]; 4],
dims: [u32; 2],
_pad: [u32; 2],
}
fn mip_count(w: u32, h: u32) -> u32 {
1 + (w.max(h) as f32).log2().floor() as u32
}
fn level_dims(w: u32, h: u32, level: u32) -> (u32, u32) {
((w >> level).max(1), (h >> level).max(1))
}
pub(crate) struct HizState {
pub(crate) dims: [u32; 2],
prev_depth_storage_view: wgpu::TextureView,
has_prev_depth: bool,
prev_view_proj: [[f32; 4]; 4],
reproj_uniform_buf: wgpu::Buffer,
all_view: wgpu::TextureView,
storage_views: Vec<wgpu::TextureView>,
copy_pipeline: wgpu::ComputePipeline,
copy_bgl: wgpu::BindGroupLayout,
init_pipeline: wgpu::ComputePipeline,
scatter_pipeline: wgpu::ComputePipeline,
to_texture_pipeline: wgpu::ComputePipeline,
reduce_pipeline: wgpu::ComputePipeline,
reproj_bg: wgpu::BindGroup,
to_texture_bg: wgpu::BindGroup,
reduce_bind_groups: Vec<wgpu::BindGroup>,
}
impl HizState {
pub(crate) fn new(device: &wgpu::Device, w: u32, h: u32) -> Self {
let w = w.max(1);
let h = h.max(1);
let mips = mip_count(w, h);
let storage_texture = |label: &str, mip_levels: u32| {
device.create_texture(&wgpu::TextureDescriptor {
label: Some(label),
size: wgpu::Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
},
mip_level_count: mip_levels,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R32Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING,
view_formats: &[],
})
};
let prev_depth = storage_texture("hiz_prev_depth", 1);
let prev_depth_storage_view = prev_depth.create_view(&wgpu::TextureViewDescriptor {
label: Some("hiz_prev_depth_storage"),
..Default::default()
});
let prev_depth_sampled_view = prev_depth.create_view(&wgpu::TextureViewDescriptor {
label: Some("hiz_prev_depth_sampled"),
..Default::default()
});
let pyramid = storage_texture("hiz_pyramid", mips);
let all_view = pyramid.create_view(&wgpu::TextureViewDescriptor::default());
let single_mip_view = |level: u32| {
pyramid.create_view(&wgpu::TextureViewDescriptor {
label: Some("hiz_mip_view"),
base_mip_level: level,
mip_level_count: Some(1),
..Default::default()
})
};
let storage_views: Vec<_> = (0..mips).map(single_mip_view).collect();
let sampled_views: Vec<_> = (0..mips).map(single_mip_view).collect();
let scatter_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("hiz_scatter_buf"),
size: (w as u64 * h as u64 * 4).max(4),
usage: wgpu::BufferUsages::STORAGE,
mapped_at_creation: false,
});
let reproj_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("hiz_reproj_uniform"),
size: std::mem::size_of::<ReprojUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let compute = wgpu::ShaderStages::COMPUTE;
let storage_tex_entry = |binding: u32| wgpu::BindGroupLayoutEntry {
binding,
visibility: compute,
ty: wgpu::BindingType::StorageTexture {
access: wgpu::StorageTextureAccess::WriteOnly,
format: wgpu::TextureFormat::R32Float,
view_dimension: wgpu::TextureViewDimension::D2,
},
count: None,
};
let sampled_tex_entry = |binding: u32| wgpu::BindGroupLayoutEntry {
binding,
visibility: compute,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
};
let buffer_entry = |binding: u32, read_only: bool| wgpu::BindGroupLayoutEntry {
binding,
visibility: compute,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
};
let uniform_entry = |binding: u32| wgpu::BindGroupLayoutEntry {
binding,
visibility: compute,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
};
let wgsl = |label: &str, src: &'static str| {
crate::resources::builders::wgsl_module(device, label, src)
};
let copy_shader = wgsl(
"hiz_copy_shader",
include_str!(concat!(env!("OUT_DIR"), "/hiz_copy.wgsl")),
);
let reproject_shader = wgsl(
"hiz_reproject_shader",
include_str!(concat!(env!("OUT_DIR"), "/hiz_reproject.wgsl")),
);
let to_texture_shader = wgsl(
"hiz_to_texture_shader",
include_str!(concat!(env!("OUT_DIR"), "/hiz_to_texture.wgsl")),
);
let reduce_shader = wgsl(
"hiz_reduce_shader",
include_str!(concat!(env!("OUT_DIR"), "/hiz_reduce.wgsl")),
);
let copy_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("hiz_copy_bgl"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: compute,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
storage_tex_entry(1),
],
});
let reproj_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("hiz_reproj_bgl"),
entries: &[
uniform_entry(0),
sampled_tex_entry(1),
buffer_entry(2, false),
],
});
let to_texture_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("hiz_to_texture_bgl"),
entries: &[buffer_entry(0, true), storage_tex_entry(1)],
});
let reduce_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("hiz_reduce_bgl"),
entries: &[sampled_tex_entry(0), storage_tex_entry(1)],
});
let pipeline =
|label: &str, bgl: &wgpu::BindGroupLayout, module: &wgpu::ShaderModule, ep: &str| {
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some(label),
bind_group_layouts: &[bgl],
push_constant_ranges: &[],
});
crate::resources::builders::compute_pipeline(device, label, &layout, module, ep)
};
let copy_pipeline = pipeline("hiz_copy_pipeline", ©_bgl, ©_shader, "copy_depth");
let init_pipeline = pipeline("hiz_init_pipeline", &reproj_bgl, &reproject_shader, "init");
let scatter_pipeline = pipeline(
"hiz_scatter_pipeline",
&reproj_bgl,
&reproject_shader,
"scatter",
);
let to_texture_pipeline = pipeline(
"hiz_to_texture_pipeline",
&to_texture_bgl,
&to_texture_shader,
"to_texture",
);
let reduce_pipeline =
pipeline("hiz_reduce_pipeline", &reduce_bgl, &reduce_shader, "reduce");
let reproj_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("hiz_reproj_bg"),
layout: &reproj_bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: reproj_uniform_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&prev_depth_sampled_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: scatter_buf.as_entire_binding(),
},
],
});
let to_texture_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("hiz_to_texture_bg"),
layout: &to_texture_bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: scatter_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&storage_views[0]),
},
],
});
let reduce_bind_groups: Vec<_> = (1..mips)
.map(|level| {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("hiz_reduce_bg"),
layout: &reduce_bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(
&sampled_views[(level - 1) as usize],
),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(
&storage_views[level as usize],
),
},
],
})
})
.collect();
Self {
dims: [w, h],
prev_depth_storage_view,
has_prev_depth: false,
prev_view_proj: [[0.0; 4]; 4],
reproj_uniform_buf,
all_view,
storage_views,
copy_pipeline,
copy_bgl,
init_pipeline,
scatter_pipeline,
to_texture_pipeline,
reduce_pipeline,
reproj_bg,
to_texture_bg,
reduce_bind_groups,
}
}
pub(crate) fn cull_view(&self) -> &wgpu::TextureView {
&self.all_view
}
pub(crate) fn store_prev_depth(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
depth_view: &wgpu::TextureView,
view_proj: [[f32; 4]; 4],
) {
let [w, h] = self.dims;
let copy_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("hiz_copy_bg"),
layout: &self.copy_bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(depth_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&self.prev_depth_storage_view),
},
],
});
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("hiz_store_prev_depth"),
timestamp_writes: None,
});
pass.set_pipeline(&self.copy_pipeline);
pass.set_bind_group(0, ©_bg, &[]);
pass.dispatch_workgroups(w.div_ceil(8), h.div_ceil(8), 1);
drop(pass);
self.prev_view_proj = view_proj;
self.has_prev_depth = true;
}
pub(crate) fn build_reprojected(
&self,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
cur_view_proj: [[f32; 4]; 4],
) -> bool {
if !self.has_prev_depth {
return false;
}
let [w, h] = self.dims;
let inv_prev_vp = glam::Mat4::from_cols_array_2d(&self.prev_view_proj)
.inverse()
.to_cols_array_2d();
let uniform = ReprojUniform {
inv_prev_vp,
cur_vp: cur_view_proj,
dims: [w, h],
_pad: [0, 0],
};
queue.write_buffer(
&self.reproj_uniform_buf,
0,
bytemuck::cast_slice(std::slice::from_ref(&uniform)),
);
{
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("hiz_reproject_init"),
timestamp_writes: None,
});
pass.set_pipeline(&self.init_pipeline);
pass.set_bind_group(0, &self.reproj_bg, &[]);
pass.dispatch_workgroups(w.div_ceil(8), h.div_ceil(8), 1);
}
{
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("hiz_reproject_scatter"),
timestamp_writes: None,
});
pass.set_pipeline(&self.scatter_pipeline);
pass.set_bind_group(0, &self.reproj_bg, &[]);
pass.dispatch_workgroups(w.div_ceil(8), h.div_ceil(8), 1);
}
{
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("hiz_reproject_to_texture"),
timestamp_writes: None,
});
pass.set_pipeline(&self.to_texture_pipeline);
pass.set_bind_group(0, &self.to_texture_bg, &[]);
pass.dispatch_workgroups(w.div_ceil(8), h.div_ceil(8), 1);
}
let mips = self.storage_views.len() as u32;
for level in 1..mips {
let (lw, lh) = level_dims(w, h, level);
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("hiz_reduce_pass"),
timestamp_writes: None,
});
pass.set_pipeline(&self.reduce_pipeline);
pass.set_bind_group(0, &self.reduce_bind_groups[(level - 1) as usize], &[]);
pass.dispatch_workgroups(lw.div_ceil(8), lh.div_ceil(8), 1);
}
true
}
}
impl crate::resources::ViewportCullState {
pub(crate) fn store_hiz_prev_depth(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
depth_view: &wgpu::TextureView,
w: u32,
h: u32,
view_proj: [[f32; 4]; 4],
) {
if w == 0 || h == 0 {
return;
}
let stale = self.hiz.as_ref().map_or(true, |s| s.dims != [w, h]);
if stale {
self.hiz = Some(HizState::new(device, w, h));
}
self.hiz
.as_mut()
.unwrap()
.store_prev_depth(device, encoder, depth_view, view_proj);
}
pub(crate) fn build_hiz_reprojected(
&self,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
cur_view_proj: [[f32; 4]; 4],
) -> bool {
match self.hiz.as_ref() {
Some(s) => s.build_reprojected(queue, encoder, cur_view_proj),
None => false,
}
}
pub(crate) fn hiz_cull_view(&self) -> Option<(&wgpu::TextureView, [f32; 2])> {
self.hiz
.as_ref()
.map(|s| (s.cull_view(), [s.dims[0] as f32, s.dims[1] as f32]))
}
}
impl crate::resources::DeviceResources {
pub(crate) fn set_occlusion_culling(&mut self, enabled: bool) {
self.occlusion_culling_enabled = enabled;
}
pub(crate) fn occlusion_culling_enabled(&self) -> bool {
self.occlusion_culling_enabled
}
}