use crate::context::Context;
use bytemuck::{Pod, Zeroable};
pub const HDR_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
pub const OIT_ACCUM_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
pub const OIT_REVEAL_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R16Float;
const BLOOM_MIPS: u32 = 5;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum Tonemap {
None,
Aces,
Reinhard,
AgX,
#[default]
Neutral,
TonyMcMapface,
}
impl Tonemap {
pub(crate) fn as_u32(self) -> u32 {
match self {
Tonemap::None => 0,
Tonemap::Aces => 1,
Tonemap::Reinhard => 2,
Tonemap::AgX => 3,
Tonemap::Neutral => 4,
Tonemap::TonyMcMapface => 5,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct HdrSettings {
pub exposure: f32,
pub tonemap: Tonemap,
pub bloom_enabled: bool,
pub bloom_threshold: f32,
pub bloom_knee: f32,
pub bloom_intensity: f32,
}
impl Default for HdrSettings {
fn default() -> Self {
HdrSettings {
exposure: 1.0,
tonemap: Tonemap::default(),
bloom_enabled: false,
bloom_threshold: 1.0,
bloom_knee: 0.5,
bloom_intensity: 0.04,
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
struct QuadVertex {
position: [f32; 2],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
struct BloomUniforms {
src_texel: [f32; 2],
threshold: f32,
knee: f32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
struct TonemapUniforms {
exposure: f32,
operator: u32,
bloom_intensity: f32,
_pad: f32,
}
struct BloomMip {
_texture: wgpu::Texture,
view: wgpu::TextureView,
width: u32,
height: u32,
}
struct HdrTargets {
scene_texture: wgpu::Texture,
scene_view: wgpu::TextureView,
scene_msaa_texture: Option<wgpu::Texture>,
scene_msaa_view: Option<wgpu::TextureView>,
bloom_mips: Vec<BloomMip>,
oit_accum_texture: wgpu::Texture,
oit_accum_view: wgpu::TextureView,
oit_reveal_texture: wgpu::Texture,
oit_reveal_view: wgpu::TextureView,
}
pub struct HdrPipeline {
settings: HdrSettings,
width: u32,
height: u32,
sample_count: u32,
_scene_texture: wgpu::Texture,
scene_view: wgpu::TextureView,
_scene_msaa_texture: Option<wgpu::Texture>,
scene_msaa_view: Option<wgpu::TextureView>,
bloom_mips: Vec<BloomMip>,
_oit_accum_texture: wgpu::Texture,
oit_accum_view: wgpu::TextureView,
_oit_reveal_texture: wgpu::Texture,
oit_reveal_view: wgpu::TextureView,
oit_layout: wgpu::BindGroupLayout,
oit_composite_pipeline: wgpu::RenderPipeline,
sampler: wgpu::Sampler,
bloom_layout: wgpu::BindGroupLayout,
prefilter_pipeline: wgpu::RenderPipeline,
downsample_pipeline: wgpu::RenderPipeline,
upsample_pipeline: wgpu::RenderPipeline,
tonemap_layout: wgpu::BindGroupLayout,
tonemap_pipeline: wgpu::RenderPipeline,
_tony_lut_texture: wgpu::Texture,
tony_lut_view: wgpu::TextureView,
tony_sampler: wgpu::Sampler,
vertex_buffer: wgpu::Buffer,
bloom_uniform: wgpu::Buffer,
tonemap_uniform: wgpu::Buffer,
}
impl HdrPipeline {
pub fn new(
width: u32,
height: u32,
sample_count: u32,
output_format: wgpu::TextureFormat,
) -> Self {
let ctxt = Context::get();
let sampler = ctxt.create_sampler(&wgpu::SamplerDescriptor {
label: Some("hdr_sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default()
});
let bloom_layout = ctxt.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("hdr_bloom_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let bloom_shader = ctxt.create_shader_module(
Some("hdr_bloom_shader"),
include_str!("../builtin/hdr_bloom.wgsl"),
);
let vertex_layout = wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<QuadVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
}],
};
let bloom_pipeline_layout = ctxt.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("hdr_bloom_pipeline_layout"),
bind_group_layouts: &[Some(&bloom_layout)],
immediate_size: 0,
});
let make_bloom_pipeline = |label: &str, fs_entry: &str, blend: Option<wgpu::BlendState>| {
ctxt.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(&bloom_pipeline_layout),
vertex: wgpu::VertexState {
module: &bloom_shader,
entry_point: Some("vs_main"),
buffers: std::slice::from_ref(&vertex_layout),
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &bloom_shader,
entry_point: Some(fs_entry),
targets: &[Some(wgpu::ColorTargetState {
format: HDR_FORMAT,
blend,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
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,
})
};
let prefilter_pipeline = make_bloom_pipeline("hdr_bloom_prefilter", "fs_prefilter", None);
let downsample_pipeline =
make_bloom_pipeline("hdr_bloom_downsample", "fs_downsample", None);
let upsample_pipeline = make_bloom_pipeline(
"hdr_bloom_upsample",
"fs_upsample",
Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent::REPLACE,
}),
);
let tonemap_layout = ctxt.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("hdr_tonemap_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
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: 6,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D3,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 7,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let tony_lut = Self::create_tony_lut(&ctxt);
let tony_lut_view = tony_lut.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D3),
..Default::default()
});
let tony_sampler = ctxt.create_sampler(&wgpu::SamplerDescriptor {
label: Some("hdr_tony_sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default()
});
let tonemap_shader = ctxt.create_shader_module(
Some("hdr_tonemap_shader"),
concat!(
include_str!("../builtin/tonemap_ops.wgsl"),
include_str!("../builtin/hdr_tonemap.wgsl"),
),
);
let tonemap_pipeline_layout =
ctxt.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("hdr_tonemap_pipeline_layout"),
bind_group_layouts: &[Some(&tonemap_layout)],
immediate_size: 0,
});
let tonemap_pipeline = ctxt.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("hdr_tonemap_pipeline"),
layout: Some(&tonemap_pipeline_layout),
vertex: wgpu::VertexState {
module: &tonemap_shader,
entry_point: Some("vs_main"),
buffers: std::slice::from_ref(&vertex_layout),
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &tonemap_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: output_format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
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,
});
let oit_layout = ctxt.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("hdr_oit_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
],
});
let oit_shader = ctxt.create_shader_module(
Some("hdr_oit_shader"),
include_str!("../builtin/hdr_oit.wgsl"),
);
let oit_pipeline_layout = ctxt.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("hdr_oit_pipeline_layout"),
bind_group_layouts: &[Some(&oit_layout)],
immediate_size: 0,
});
let oit_composite_pipeline = ctxt.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("hdr_oit_composite_pipeline"),
layout: Some(&oit_pipeline_layout),
vertex: wgpu::VertexState {
module: &oit_shader,
entry_point: Some("vs_main"),
buffers: std::slice::from_ref(&vertex_layout),
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &oit_shader,
entry_point: Some("fs_composite"),
targets: &[Some(wgpu::ColorTargetState {
format: HDR_FORMAT,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent::REPLACE,
}),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
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,
});
let vertices = [
QuadVertex {
position: [-1.0, -1.0],
},
QuadVertex {
position: [1.0, -1.0],
},
QuadVertex {
position: [-1.0, 1.0],
},
QuadVertex {
position: [1.0, 1.0],
},
];
let vertex_buffer = ctxt.create_buffer_init(
Some("hdr_vertex_buffer"),
bytemuck::cast_slice(&vertices),
wgpu::BufferUsages::VERTEX,
);
let bloom_uniform = ctxt.create_buffer_simple(
Some("hdr_bloom_uniform"),
std::mem::size_of::<BloomUniforms>() as u64,
wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
);
let tonemap_uniform = ctxt.create_buffer_simple(
Some("hdr_tonemap_uniform"),
std::mem::size_of::<TonemapUniforms>() as u64,
wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
);
let targets = Self::create_targets(width, height, sample_count);
HdrPipeline {
settings: HdrSettings::default(),
width,
height,
sample_count,
_scene_texture: targets.scene_texture,
scene_view: targets.scene_view,
_scene_msaa_texture: targets.scene_msaa_texture,
scene_msaa_view: targets.scene_msaa_view,
bloom_mips: targets.bloom_mips,
_oit_accum_texture: targets.oit_accum_texture,
oit_accum_view: targets.oit_accum_view,
_oit_reveal_texture: targets.oit_reveal_texture,
oit_reveal_view: targets.oit_reveal_view,
oit_layout,
oit_composite_pipeline,
sampler,
bloom_layout,
prefilter_pipeline,
downsample_pipeline,
upsample_pipeline,
tonemap_layout,
tonemap_pipeline,
_tony_lut_texture: tony_lut,
tony_lut_view,
tony_sampler,
vertex_buffer,
bloom_uniform,
tonemap_uniform,
}
}
pub(crate) fn create_tony_lut(ctxt: &Context) -> wgpu::Texture {
const DIM: u32 = 48;
let data: &[u8] = include_bytes!("../builtin/tony_mc_mapface.bin");
let tex = ctxt.create_texture(&wgpu::TextureDescriptor {
label: Some("hdr_tony_lut"),
size: wgpu::Extent3d {
width: DIM,
height: DIM,
depth_or_array_layers: DIM,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D3,
format: wgpu::TextureFormat::Rgba16Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
ctxt.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &tex,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(DIM * 8), rows_per_image: Some(DIM),
},
wgpu::Extent3d {
width: DIM,
height: DIM,
depth_or_array_layers: DIM,
},
);
tex
}
fn create_targets(width: u32, height: u32, sample_count: u32) -> HdrTargets {
let ctxt = Context::get();
let width = width.max(1);
let height = height.max(1);
let sample_count = sample_count.max(1);
let scene_texture = ctxt.create_texture(&wgpu::TextureDescriptor {
label: Some("hdr_scene_texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: HDR_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let scene_view = scene_texture.create_view(&wgpu::TextureViewDescriptor::default());
let (scene_msaa_texture, scene_msaa_view) = if sample_count > 1 {
let msaa = ctxt.create_texture(&wgpu::TextureDescriptor {
label: Some("hdr_scene_msaa_texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count,
dimension: wgpu::TextureDimension::D2,
format: HDR_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let view = msaa.create_view(&wgpu::TextureViewDescriptor::default());
(Some(msaa), Some(view))
} else {
(None, None)
};
let mut bloom_mips = Vec::with_capacity(BLOOM_MIPS as usize);
let mut mw = width;
let mut mh = height;
for i in 0..BLOOM_MIPS {
mw = (mw / 2).max(1);
mh = (mh / 2).max(1);
let tex = ctxt.create_texture(&wgpu::TextureDescriptor {
label: Some("hdr_bloom_mip"),
size: wgpu::Extent3d {
width: mw,
height: mh,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: HDR_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
bloom_mips.push(BloomMip {
_texture: tex,
view,
width: mw,
height: mh,
});
let _ = i;
}
let make_oit = |label: &str, format: wgpu::TextureFormat| {
let tex = ctxt.create_texture(&wgpu::TextureDescriptor {
label: Some(label),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
(tex, view)
};
let (oit_accum_texture, oit_accum_view) = make_oit("hdr_oit_accum", OIT_ACCUM_FORMAT);
let (oit_reveal_texture, oit_reveal_view) = make_oit("hdr_oit_reveal", OIT_REVEAL_FORMAT);
HdrTargets {
scene_texture,
scene_view,
scene_msaa_texture,
scene_msaa_view,
bloom_mips,
oit_accum_texture,
oit_accum_view,
oit_reveal_texture,
oit_reveal_view,
}
}
pub fn resize(&mut self, width: u32, height: u32, sample_count: u32) {
let width = width.max(1);
let height = height.max(1);
let sample_count = sample_count.max(1);
if self.width == width && self.height == height && self.sample_count == sample_count {
return;
}
let targets = Self::create_targets(width, height, sample_count);
self._scene_texture = targets.scene_texture;
self.scene_view = targets.scene_view;
self._scene_msaa_texture = targets.scene_msaa_texture;
self.scene_msaa_view = targets.scene_msaa_view;
self.bloom_mips = targets.bloom_mips;
self._oit_accum_texture = targets.oit_accum_texture;
self.oit_accum_view = targets.oit_accum_view;
self._oit_reveal_texture = targets.oit_reveal_texture;
self.oit_reveal_view = targets.oit_reveal_view;
self.width = width;
self.height = height;
self.sample_count = sample_count;
}
pub fn scene_render_view(&self) -> &wgpu::TextureView {
self.scene_msaa_view.as_ref().unwrap_or(&self.scene_view)
}
pub fn scene_resolve_view(&self) -> Option<&wgpu::TextureView> {
if self.scene_msaa_view.is_some() {
Some(&self.scene_view)
} else {
None
}
}
pub fn oit_accum_view(&self) -> &wgpu::TextureView {
&self.oit_accum_view
}
pub fn oit_reveal_view(&self) -> &wgpu::TextureView {
&self.oit_reveal_view
}
pub fn composite_oit(&self, encoder: &mut wgpu::CommandEncoder) {
let ctxt = Context::get();
let bind_group = ctxt.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("hdr_oit_composite_bind_group"),
layout: &self.oit_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.oit_accum_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&self.oit_reveal_view),
},
],
});
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("hdr_oit_composite_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.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: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(&self.oit_composite_pipeline);
pass.set_bind_group(0, &bind_group, &[]);
pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
pass.draw(0..4, 0..1);
}
pub fn settings_mut(&mut self) -> &mut HdrSettings {
&mut self.settings
}
pub fn settings(&self) -> &HdrSettings {
&self.settings
}
fn bloom_bind_group(
&self,
ctxt: &Context,
src: &wgpu::TextureView,
src_w: u32,
src_h: u32,
) -> wgpu::BindGroup {
ctxt.write_buffer(
&self.bloom_uniform,
0,
bytemuck::bytes_of(&BloomUniforms {
src_texel: [1.0 / src_w.max(1) as f32, 1.0 / src_h.max(1) as f32],
threshold: self.settings.bloom_threshold,
knee: self.settings.bloom_knee,
}),
);
ctxt.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("hdr_bloom_bind_group"),
layout: &self.bloom_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(src),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.bloom_uniform.as_entire_binding(),
},
],
})
}
fn run_bloom(&self, encoder: &mut wgpu::CommandEncoder) {
let ctxt = Context::get();
{
let bg = self.bloom_bind_group(&ctxt, &self.scene_view, self.width, self.height);
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("hdr_bloom_prefilter_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.bloom_mips[0].view,
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: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(&self.prefilter_pipeline);
pass.set_bind_group(0, &bg, &[]);
pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
pass.draw(0..4, 0..1);
}
for i in 0..self.bloom_mips.len() - 1 {
let src = &self.bloom_mips[i];
let dst = &self.bloom_mips[i + 1];
let bg = self.bloom_bind_group(&ctxt, &src.view, src.width, src.height);
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("hdr_bloom_downsample_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &dst.view,
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: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(&self.downsample_pipeline);
pass.set_bind_group(0, &bg, &[]);
pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
pass.draw(0..4, 0..1);
}
for i in (0..self.bloom_mips.len() - 1).rev() {
let src = &self.bloom_mips[i + 1];
let dst = &self.bloom_mips[i];
let bg = self.bloom_bind_group(&ctxt, &src.view, src.width, src.height);
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("hdr_bloom_upsample_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &dst.view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(&self.upsample_pipeline);
pass.set_bind_group(0, &bg, &[]);
pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
pass.draw(0..4, 0..1);
}
}
pub fn resolve(&self, encoder: &mut wgpu::CommandEncoder, output_view: &wgpu::TextureView) {
let ctxt = Context::get();
let bloom_enabled = self.settings.bloom_enabled && self.settings.bloom_intensity > 0.0;
if bloom_enabled {
self.run_bloom(encoder);
}
ctxt.write_buffer(
&self.tonemap_uniform,
0,
bytemuck::bytes_of(&TonemapUniforms {
exposure: self.settings.exposure,
operator: self.settings.tonemap.as_u32(),
bloom_intensity: if bloom_enabled {
self.settings.bloom_intensity
} else {
0.0
},
_pad: 0.0,
}),
);
let bloom_view = &self.bloom_mips[0].view;
let bind_group = ctxt.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("hdr_tonemap_bind_group"),
layout: &self.tonemap_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.scene_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(bloom_view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
wgpu::BindGroupEntry {
binding: 4,
resource: self.tonemap_uniform.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::TextureView(&self.tony_lut_view),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::Sampler(&self.tony_sampler),
},
],
});
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("hdr_tonemap_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: output_view,
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: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(&self.tonemap_pipeline);
pass.set_bind_group(0, &bind_group, &[]);
pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
pass.draw(0..4, 0..1);
}
}