pub struct EffectPipeline {
pub pipeline: wgpu::RenderPipeline,
pub bind_group_layout: wgpu::BindGroupLayout,
}
impl std::fmt::Debug for EffectPipeline {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EffectPipeline").finish_non_exhaustive()
}
}
pub struct EffectCache {
pub uniform_bufs: Vec<wgpu::Buffer>,
pub bind_groups: Vec<[wgpu::BindGroup; 2]>,
pub aux_textures: Vec<wgpu::Texture>,
pub aux_views: Vec<wgpu::TextureView>,
pub aux_pipelines: Vec<wgpu::RenderPipeline>,
}
pub fn create_blit_pipeline(
device: &wgpu::Device,
format: wgpu::TextureFormat,
label: &str,
) -> EffectPipeline {
create_filter_pipeline(
device,
format,
label,
include_str!("../../shaders/blit.wgsl"),
"fs_blit",
)
}
pub fn create_downscale_pipeline(
device: &wgpu::Device,
format: wgpu::TextureFormat,
label: &str,
) -> EffectPipeline {
create_filter_pipeline(
device,
format,
label,
include_str!("../../shaders/downscale.wgsl"),
"fs_downscale",
)
}
fn create_filter_pipeline(
device: &wgpu::Device,
format: wgpu::TextureFormat,
label: &str,
shader_source: &str,
fragment_entry: &str,
) -> EffectPipeline {
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some(&format!("{label}-bgl")),
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 pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some(&format!("{label}-layout")),
bind_group_layouts: &[Some(&bind_group_layout)],
immediate_size: 0,
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(&format!("{label}-shader")),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some(fragment_entry),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
EffectPipeline {
pipeline,
bind_group_layout,
}
}
pub struct MaskedFilterPipeline {
plain_rgba: wgpu::RenderPipeline,
plain_r8: wgpu::RenderPipeline,
masked_rgba: wgpu::RenderPipeline,
masked_r8: wgpu::RenderPipeline,
plain_bgl: wgpu::BindGroupLayout,
masked_bgl: wgpu::BindGroupLayout,
}
impl std::fmt::Debug for MaskedFilterPipeline {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MaskedFilterPipeline")
.finish_non_exhaustive()
}
}
fn load_tex_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
}
}
impl MaskedFilterPipeline {
pub fn new(
device: &wgpu::Device,
label: &str,
shader_source: &str,
plain_entry: &str,
masked_entry: &str,
) -> MaskedFilterPipeline {
let plain_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some(&format!("{label}-plain-bgl")),
entries: &[load_tex_entry(0)],
});
let masked_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some(&format!("{label}-masked-bgl")),
entries: &[load_tex_entry(0), load_tex_entry(1)],
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(&format!("{label}-shader")),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let make =
|bgl: &wgpu::BindGroupLayout, entry: &str, format: wgpu::TextureFormat, lbl: &str| {
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some(lbl),
bind_group_layouts: &[Some(bgl)],
immediate_size: 0,
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(lbl),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some(entry),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
})
};
let rgba = wgpu::TextureFormat::Rgba8Unorm;
let r8 = wgpu::TextureFormat::R8Unorm;
MaskedFilterPipeline {
plain_rgba: make(
&plain_bgl,
plain_entry,
rgba,
&format!("{label}-plain-rgba"),
),
plain_r8: make(&plain_bgl, plain_entry, r8, &format!("{label}-plain-r8")),
masked_rgba: make(
&masked_bgl,
masked_entry,
rgba,
&format!("{label}-masked-rgba"),
),
masked_r8: make(&masked_bgl, masked_entry, r8, &format!("{label}-masked-r8")),
plain_bgl,
masked_bgl,
}
}
pub fn render(
&self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
src_view: &wgpu::TextureView,
mask_view: Option<&wgpu::TextureView>,
out_view: &wgpu::TextureView,
format: wgpu::TextureFormat,
) {
let is_r8 = format == wgpu::TextureFormat::R8Unorm;
let (pipeline, bind_group) = match mask_view {
Some(mv) => {
let pipeline = if is_r8 {
&self.masked_r8
} else {
&self.masked_rgba
};
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("masked-filter-bg"),
layout: &self.masked_bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(src_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(mv),
},
],
});
(pipeline, bind_group)
}
None => {
let pipeline = if is_r8 {
&self.plain_r8
} else {
&self.plain_rgba
};
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("plain-filter-bg"),
layout: &self.plain_bgl,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(src_view),
}],
});
(pipeline, bind_group)
}
};
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("masked-filter-pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: out_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(pipeline);
pass.set_bind_group(0, &bind_group, &[]);
pass.draw(0..3, 0..1);
}
}
pub fn create_blit_bind_group(
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
source_view: &wgpu::TextureView,
sampler: &wgpu::Sampler,
label: &str,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(source_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(sampler),
},
],
})
}