use crate::context::Context;
use bytemuck::{Pod, Zeroable};
use glamx::Mat4;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum DepthOfFieldMode {
Bokeh,
Gaussian,
}
#[derive(Copy, Clone, Debug)]
pub struct DofSettings {
pub mode: DepthOfFieldMode,
pub focal_distance: f32,
pub aperture_f_stops: f32,
pub sensor_height: f32,
pub max_coc_diameter: f32,
pub max_depth: f32,
pub num_taps: u32,
}
impl Default for DofSettings {
fn default() -> Self {
DofSettings {
mode: DepthOfFieldMode::Bokeh,
focal_distance: 10.0,
aperture_f_stops: 1.0 / 8.0,
sensor_height: 0.018_66,
max_coc_diameter: 64.0,
max_depth: 1.0e6,
num_taps: 48,
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct DofUniforms {
proj: [[f32; 4]; 4],
params0: [f32; 4],
params1: [f32; 4],
params2: [f32; 4],
}
pub struct Dof {
settings: DofSettings,
width: u32,
height: u32,
_chain_texture: wgpu::Texture,
chain_view: wgpu::TextureView,
chain_mips: u32,
sampler: wgpu::Sampler,
coc_layout: wgpu::BindGroupLayout,
coc_pipeline: wgpu::RenderPipeline,
downsample_layout: wgpu::BindGroupLayout,
downsample_pipeline: wgpu::RenderPipeline,
gather_layout: wgpu::BindGroupLayout,
gather_pipeline: wgpu::RenderPipeline,
uniform: wgpu::Buffer,
}
impl Dof {
pub fn new(width: u32, height: u32) -> Dof {
let ctxt = Context::get();
let w = width.max(1);
let h = height.max(1);
let sampler = ctxt.create_sampler(&wgpu::SamplerDescriptor {
label: Some("dof_sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Linear,
..Default::default()
});
let (chain_texture, chain_view, chain_mips) = Self::make_chain(w, h);
let shader = ctxt.create_shader_module(
Some("dof"),
&crate::builtin::compile_shader_with_common(
"package::dof",
include_str!("../builtin/dof.wgsl"),
),
);
let coc_layout = make_layout("dof_coc_layout");
let gather_layout = make_layout("dof_gather_layout");
let coc_pipeline =
make_fullscreen_pipeline("dof_coc", &shader, "fs_coc", &coc_layout, None);
let gather_pipeline =
make_fullscreen_pipeline("dof_gather", &shader, "fs_gather", &gather_layout, None);
let downsample_shader = ctxt.create_shader_module(
Some("dof_downsample"),
&crate::builtin::compile_shader_with_common(
"package::env_downsample",
crate::builtin::ENV_DOWNSAMPLE_WESL,
),
);
let downsample_layout = ctxt.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("dof_downsample_layout"),
entries: &[
tex_entry(0),
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let downsample_pipeline = make_downsample_pipeline(&downsample_shader, &downsample_layout);
let uniform = ctxt.create_buffer_simple(
Some("dof_uniform"),
std::mem::size_of::<DofUniforms>() as u64,
wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
);
Dof {
settings: DofSettings::default(),
width: w,
height: h,
_chain_texture: chain_texture,
chain_view,
chain_mips,
sampler,
coc_layout,
coc_pipeline,
downsample_layout,
downsample_pipeline,
gather_layout,
gather_pipeline,
uniform,
}
}
fn make_chain(w: u32, h: u32) -> (wgpu::Texture, wgpu::TextureView, u32) {
let ctxt = Context::get();
let mips = (32 - w.max(h).leading_zeros()).clamp(1, 7);
let texture = ctxt.create_texture(&wgpu::TextureDescriptor {
label: Some("dof_chain"),
size: wgpu::Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
},
mip_level_count: mips,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: crate::post_processing::HDR_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
(texture, view, mips)
}
pub fn resize(&mut self, width: u32, height: u32) {
let w = width.max(1);
let h = height.max(1);
if self.width == w && self.height == h {
return;
}
let (tex, view, mips) = Self::make_chain(w, h);
self._chain_texture = tex;
self.chain_view = view;
self.chain_mips = mips;
self.width = w;
self.height = h;
}
pub fn settings_mut(&mut self) -> &mut DofSettings {
&mut self.settings
}
pub fn settings(&self) -> &DofSettings {
&self.settings
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute(
&self,
encoder: &mut wgpu::CommandEncoder,
scene_view: &wgpu::TextureView,
viewpos: &wgpu::TextureView,
proj: Mat4,
background_depth: f32,
gpu: &mut crate::renderer::timings::GpuTimer,
) {
let ctxt = Context::get();
let s = &self.settings;
ctxt.write_buffer(
&self.uniform,
0,
bytemuck::bytes_of(&DofUniforms {
proj: proj.to_cols_array_2d(),
params0: [
1.0 / self.width as f32,
1.0 / self.height as f32,
self.height as f32,
(self.chain_mips.max(1) - 1) as f32,
],
params1: [
s.focal_distance,
s.aperture_f_stops,
s.sensor_height,
s.max_coc_diameter,
],
params2: [
s.max_depth,
background_depth,
if s.mode == DepthOfFieldMode::Gaussian {
1.0
} else {
0.0
},
s.num_taps.max(1) as f32,
],
}),
);
let mip0 = self
._chain_texture
.create_view(&wgpu::TextureViewDescriptor {
label: Some("dof_chain_mip0"),
base_mip_level: 0,
mip_level_count: Some(1),
..Default::default()
});
{
let bg = ctxt.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("dof_coc_bg"),
layout: &self.coc_layout,
entries: &[
tex_bind(0, scene_view),
tex_bind(1, viewpos),
samp_bind(2, &self.sampler),
uniform_bind(3, &self.uniform),
],
});
let dof_ts = gpu.render_scope("dof");
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("dof_coc_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &mip0,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: dof_ts,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(&self.coc_pipeline);
pass.set_bind_group(0, &bg, &[]);
pass.draw(0..3, 0..1);
}
for mip in 1..self.chain_mips {
let src = self
._chain_texture
.create_view(&wgpu::TextureViewDescriptor {
label: Some("dof_chain_src"),
base_mip_level: mip - 1,
mip_level_count: Some(1),
..Default::default()
});
let dst = self
._chain_texture
.create_view(&wgpu::TextureViewDescriptor {
label: Some("dof_chain_dst"),
base_mip_level: mip,
mip_level_count: Some(1),
..Default::default()
});
let bg = ctxt.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("dof_downsample_bg"),
layout: &self.downsample_layout,
entries: &[tex_bind(0, &src), samp_bind(1, &self.sampler)],
});
let dof_ts = gpu.render_scope("dof");
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("dof_downsample_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &dst,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: dof_ts,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(&self.downsample_pipeline);
pass.set_bind_group(0, &bg, &[]);
pass.draw(0..3, 0..1);
}
{
let bg = ctxt.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("dof_gather_bg"),
layout: &self.gather_layout,
entries: &[
tex_bind(0, &self.chain_view),
tex_bind(1, &self.chain_view),
samp_bind(2, &self.sampler),
uniform_bind(3, &self.uniform),
],
});
let dof_ts = gpu.render_scope("dof");
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("dof_gather_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: scene_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: dof_ts,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(&self.gather_pipeline);
pass.set_bind_group(0, &bg, &[]);
pass.draw(0..3, 0..1);
}
}
}
fn make_layout(label: &str) -> wgpu::BindGroupLayout {
let ctxt = Context::get();
ctxt.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some(label),
entries: &[
tex_entry(0),
tex_entry(1),
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
})
}
fn tex_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
}
}
fn tex_bind(binding: u32, view: &wgpu::TextureView) -> wgpu::BindGroupEntry<'_> {
wgpu::BindGroupEntry {
binding,
resource: wgpu::BindingResource::TextureView(view),
}
}
fn samp_bind(binding: u32, sampler: &wgpu::Sampler) -> wgpu::BindGroupEntry<'_> {
wgpu::BindGroupEntry {
binding,
resource: wgpu::BindingResource::Sampler(sampler),
}
}
fn uniform_bind(binding: u32, buffer: &wgpu::Buffer) -> wgpu::BindGroupEntry<'_> {
wgpu::BindGroupEntry {
binding,
resource: buffer.as_entire_binding(),
}
}
fn make_fullscreen_pipeline(
label: &str,
shader: &wgpu::ShaderModule,
fs_entry: &str,
layout: &wgpu::BindGroupLayout,
blend: Option<wgpu::BlendState>,
) -> wgpu::RenderPipeline {
let ctxt = Context::get();
let pl = ctxt.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some(label),
bind_group_layouts: &[Some(layout)],
immediate_size: 0,
});
ctxt.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(&pl),
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_entry),
targets: &[Some(wgpu::ColorTargetState {
format: crate::post_processing::HDR_FORMAT,
blend,
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: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
})
}
fn make_downsample_pipeline(
shader: &wgpu::ShaderModule,
layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline {
let ctxt = Context::get();
let pl = ctxt.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("dof_downsample"),
bind_group_layouts: &[Some(layout)],
immediate_size: 0,
});
ctxt.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("dof_downsample"),
layout: Some(&pl),
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: crate::post_processing::HDR_FORMAT,
blend: None,
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: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
})
}