use bytemuck::{Pod, Zeroable};
use crate::context::Context;
use super::accumulation::Accumulation;
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
struct DenoiseUniforms {
width: u32,
height: u32,
step: i32,
demodulate: u32,
remodulate: u32,
sigma_normal: f32,
sigma_luminance: f32,
_pad0: f32,
}
const PIXEL_SIZE: u64 = 16;
pub struct Denoise {
pipeline: wgpu::ComputePipeline,
bind_group_layout: wgpu::BindGroupLayout,
uniforms: Vec<wgpu::Buffer>,
scratch: [wgpu::Buffer; 2],
width: u32,
height: u32,
}
impl Denoise {
pub fn new() -> Denoise {
let ctxt = Context::get();
let bind_group_layout = ctxt.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("rt_denoise_bind_group_layout"),
entries: &[
storage_entry(0, true), storage_entry(1, false), storage_entry(2, true), uniform_entry(3),
],
});
let pipeline_layout = ctxt.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("rt_denoise_pipeline_layout"),
bind_group_layouts: &[Some(&bind_group_layout)],
immediate_size: 0,
});
let shader = ctxt.create_shader_module(
Some("rt_denoise_shader"),
include_str!("../../builtin/raytrace/denoise.wgsl"),
);
let pipeline = ctxt
.device
.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("rt_denoise_pipeline"),
layout: Some(&pipeline_layout),
module: &shader,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
});
Denoise {
pipeline,
bind_group_layout,
uniforms: Vec::new(),
scratch: [
Self::make_scratch(1, 1, "rt_denoise_scratch0"),
Self::make_scratch(1, 1, "rt_denoise_scratch1"),
],
width: 1,
height: 1,
}
}
fn make_scratch(width: u32, height: u32, label: &str) -> wgpu::Buffer {
let count = (width.max(1) as u64) * (height.max(1) as u64);
Context::get().create_buffer_simple(
Some(label),
count * PIXEL_SIZE,
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
)
}
fn ensure(&mut self, width: u32, height: u32) {
if width == self.width && height == self.height {
return;
}
self.scratch = [
Self::make_scratch(width, height, "rt_denoise_scratch0"),
Self::make_scratch(width, height, "rt_denoise_scratch1"),
];
self.width = width;
self.height = height;
}
fn ensure_uniforms(&mut self, n: usize) {
let ctxt = Context::get();
while self.uniforms.len() < n {
self.uniforms.push(ctxt.create_buffer_simple(
Some("rt_denoise_uniform"),
std::mem::size_of::<DenoiseUniforms>() as u64,
wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
));
}
}
pub fn run<'a>(
&'a mut self,
encoder: &mut wgpu::CommandEncoder,
accum: &'a Accumulation,
iterations: u32,
) -> &'a wgpu::Buffer {
let ctxt = Context::get();
let width = accum.width;
let height = accum.height;
self.ensure(width, height);
let iterations = iterations.max(1) as usize;
self.ensure_uniforms(iterations);
for i in 0..iterations {
let step = 1i32 << i as u32;
let demodulate = (i == 0) as u32;
let remodulate = (i == iterations - 1) as u32;
ctxt.write_buffer(
&self.uniforms[i],
0,
bytemuck::bytes_of(&DenoiseUniforms {
width,
height,
step,
demodulate,
remodulate,
sigma_normal: 64.0,
sigma_luminance: 4.0,
_pad0: 0.0,
}),
);
let src = if i == 0 {
&accum.buffer
} else {
&self.scratch[(i - 1) % 2]
};
let dst = &self.scratch[i % 2];
let bind_group = ctxt.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("rt_denoise_bind_group"),
layout: &self.bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: src.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: dst.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: accum.buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: self.uniforms[i].as_entire_binding(),
},
],
});
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("rt_denoise_pass"),
timestamp_writes: None,
});
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &bind_group, &[]);
pass.dispatch_workgroups(width.div_ceil(8), height.div_ceil(8), 1);
}
&self.scratch[(iterations - 1) % 2]
}
}
fn uniform_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}
fn storage_entry(binding: u32, read_only: bool) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}