use crate::context::Context;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TransmissionBlurQuality {
Low,
Medium,
#[default]
High,
}
impl TransmissionBlurQuality {
fn pipeline_index(self) -> usize {
match self {
TransmissionBlurQuality::Low => 0,
TransmissionBlurQuality::Medium => 1,
TransmissionBlurQuality::High => 2,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct TransmissionSettings {
pub blur_quality: TransmissionBlurQuality,
pub steps: u32,
}
impl Default for TransmissionSettings {
fn default() -> Self {
TransmissionSettings {
blur_quality: TransmissionBlurQuality::default(),
steps: 1,
}
}
}
pub struct Transmission {
settings: TransmissionSettings,
width: u32,
height: u32,
_texture: wgpu::Texture,
view: wgpu::TextureView,
mips: u32,
sampler: wgpu::Sampler,
downsample_layout: wgpu::BindGroupLayout,
downsample_pipelines: [wgpu::RenderPipeline; 3],
}
impl Transmission {
pub fn new(width: u32, height: u32) -> Transmission {
let ctxt = Context::get();
let w = width.max(1);
let h = height.max(1);
let sampler = ctxt.create_sampler(&wgpu::SamplerDescriptor {
label: Some("transmission_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 (texture, view, mips) = Self::make_chain(w, h);
let downsample_shader = ctxt.create_shader_module(
Some("transmission_downsample"),
&crate::builtin::compile_shader_with_common(
"package::transmission_downsample",
include_str!("../builtin/transmission_downsample.wgsl"),
),
);
let downsample_layout = ctxt.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("transmission_downsample_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,
},
],
});
let pl = ctxt.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("transmission_downsample"),
bind_group_layouts: &[Some(&downsample_layout)],
immediate_size: 0,
});
let make = |entry: &str| {
ctxt.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("transmission_downsample"),
layout: Some(&pl),
vertex: wgpu::VertexState {
module: &downsample_shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &downsample_shader,
entry_point: Some(entry),
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,
})
};
let downsample_pipelines = [make("fs_low"), make("fs_medium"), make("fs_high")];
Transmission {
settings: TransmissionSettings::default(),
width: w,
height: h,
_texture: texture,
view,
mips,
sampler,
downsample_layout,
downsample_pipelines,
}
}
pub fn settings_mut(&mut self) -> &mut TransmissionSettings {
&mut self.settings
}
pub fn steps(&self) -> u32 {
self.settings.steps.max(1)
}
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("transmission_background_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._texture = tex;
self.view = view;
self.mips = mips;
self.width = w;
self.height = h;
}
pub fn view(&self) -> &wgpu::TextureView {
&self.view
}
pub fn max_lod(&self) -> f32 {
(self.mips.max(1) - 1) as f32
}
pub(crate) fn build(
&self,
encoder: &mut wgpu::CommandEncoder,
scene_view: &wgpu::TextureView,
gpu: &mut crate::renderer::timings::GpuTimer,
) {
let ctxt = Context::get();
for mip in 0..self.mips {
let prev_view = if mip == 0 {
None
} else {
Some(self._texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("transmission_bg_src"),
base_mip_level: mip - 1,
mip_level_count: Some(1),
..Default::default()
}))
};
let src_ref: &wgpu::TextureView = prev_view.as_ref().unwrap_or(scene_view);
let dst = self._texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("transmission_bg_dst"),
base_mip_level: mip,
mip_level_count: Some(1),
..Default::default()
});
let bg = ctxt.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("transmission_downsample_bg"),
layout: &self.downsample_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(src_ref),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
],
});
let ts = gpu.render_scope("transmission");
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("transmission_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: ts,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(
&self.downsample_pipelines[self.settings.blur_quality.pipeline_index()],
);
pass.set_bind_group(0, &bg, &[]);
pass.draw(0..3, 0..1);
}
}
}