use crate::images::IMAGE_FORMAT;
pub(crate) struct MipGenerator {
pipeline: wgpu::RenderPipeline,
layout: wgpu::BindGroupLayout,
sampler: wgpu::Sampler,
}
const MIP_SHADER: &str = r#"
struct VsOut { @builtin(position) pos: vec4<f32>, @location(0) uv: vec2<f32> };
@vertex fn vs(@builtin(vertex_index) vi: u32) -> VsOut {
// Fullscreen triangle.
let xy = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
var out: VsOut;
out.pos = vec4<f32>(xy * 2.0 - 1.0, 0.0, 1.0);
out.uv = vec2<f32>(xy.x, 1.0 - xy.y);
return out;
}
@group(0) @binding(0) var t: texture_2d<f32>;
@group(0) @binding(1) var s: sampler;
@fragment fn fs(in: VsOut) -> @location(0) vec4<f32> {
return textureSample(t, s, in.uv);
}
"#;
pub(crate) fn full_mip_count(size: [u32; 2]) -> u32 {
32 - size[0].max(size[1]).max(1).leading_zeros()
}
impl MipGenerator {
pub(crate) fn new(device: &wgpu::Device) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("valo.mips"),
source: wgpu::ShaderSource::Wgsl(MIP_SHADER.into()),
});
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("valo.mips"),
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("valo.mips"),
bind_group_layouts: &[Some(&layout)],
immediate_size: 0,
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("valo.mips"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs"),
compilation_options: Default::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format: IMAGE_FORMAT,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("valo.mips"),
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
Self {
pipeline,
layout,
sampler,
}
}
pub(crate) fn generate(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
texture: &wgpu::Texture,
mip_levels: u32,
) {
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("valo.mips"),
});
for level in 1..mip_levels {
let source = level_view(texture, level - 1);
let target = level_view(texture, level);
self.blit(device, &mut encoder, &source, &target);
}
queue.submit(std::iter::once(encoder.finish()));
}
pub(crate) fn copy_level_zero(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
source: &wgpu::Texture,
target: &wgpu::Texture,
) {
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("valo.image.copy"),
});
self.blit(
device,
&mut encoder,
&level_view(source, 0),
&level_view(target, 0),
);
queue.submit(std::iter::once(encoder.finish()));
}
fn blit(
&self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
source: &wgpu::TextureView,
target: &wgpu::TextureView,
) {
let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("valo.mips"),
layout: &self.layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(source),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
],
});
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("valo.mips"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &bind, &[]);
pass.draw(0..3, 0..1);
}
}
fn level_view(texture: &wgpu::Texture, level: u32) -> wgpu::TextureView {
texture.create_view(&wgpu::TextureViewDescriptor {
base_mip_level: level,
mip_level_count: Some(1),
..Default::default()
})
}