use agg_gui::geometry::Rect;
use wgpu::util::DeviceExt;
use crate::pipelines::{TexUniforms, WgpuPipelines};
pub fn ssaa_linear_scale(requested_samples: u32) -> u32 {
match requested_samples {
0 | 1 => 1,
2..=5 => 2,
6..=12 => 3,
_ => 4,
}
}
pub struct SsaaFramebuffer {
color: (wgpu::Texture, wgpu::TextureView),
depth: Option<(wgpu::Texture, wgpu::TextureView)>,
format: wgpu::TextureFormat,
with_depth: bool,
width: u32,
height: u32,
blit_sampler: wgpu::Sampler,
}
impl SsaaFramebuffer {
pub fn new(
device: &wgpu::Device,
w: u32,
h: u32,
format: wgpu::TextureFormat,
with_depth: bool,
) -> Self {
let blit_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("ssaa_blit"),
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 mut fb = Self {
color: alloc_color(device, w.max(1), h.max(1), format),
depth: None,
format,
with_depth,
width: w.max(1),
height: h.max(1),
blit_sampler,
};
if with_depth {
fb.depth = Some(alloc_depth(device, fb.width, fb.height));
}
fb
}
pub fn ensure_size(&mut self, device: &wgpu::Device, w: u32, h: u32) {
let w = w.max(1);
let h = h.max(1);
if w == self.width && h == self.height {
return;
}
self.width = w;
self.height = h;
self.color = alloc_color(device, w, h, self.format);
if self.with_depth {
self.depth = Some(alloc_depth(device, w, h));
}
}
pub fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn render_view(&self) -> &wgpu::TextureView {
&self.color.1
}
pub fn resolve_view(&self) -> &wgpu::TextureView {
&self.color.1
}
pub fn resolve_texture(&self) -> &wgpu::Texture {
&self.color.0
}
pub fn depth_view(&self) -> Option<&wgpu::TextureView> {
self.depth.as_ref().map(|(_, v)| v)
}
pub fn blit_to(
&self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target_view: &wgpu::TextureView,
target_size: (u32, u32),
dst_rect: Rect,
parent_clip: Option<[i32; 4]>,
pipelines: &WgpuPipelines,
) {
self.blit_to_inner(
device,
encoder,
target_view,
target_size,
dst_rect,
parent_clip,
&pipelines.tex_pipeline,
pipelines,
);
}
pub fn blit_downsample_3x_to(
&self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target_view: &wgpu::TextureView,
target_size: (u32, u32),
dst_rect: Rect,
parent_clip: Option<[i32; 4]>,
pipelines: &WgpuPipelines,
) {
self.blit_to_inner(
device,
encoder,
target_view,
target_size,
dst_rect,
parent_clip,
&pipelines.tex_downsample_3x_pipeline,
pipelines,
);
}
pub fn blit_downsample_4x_to(
&self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target_view: &wgpu::TextureView,
target_size: (u32, u32),
dst_rect: Rect,
parent_clip: Option<[i32; 4]>,
pipelines: &WgpuPipelines,
) {
self.blit_to_inner(
device,
encoder,
target_view,
target_size,
dst_rect,
parent_clip,
&pipelines.tex_downsample_4x_pipeline,
pipelines,
);
}
#[allow(clippy::too_many_arguments)]
pub fn blit_downsample_to(
&self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target_view: &wgpu::TextureView,
target_size: (u32, u32),
dst_rect: Rect,
parent_clip: Option<[i32; 4]>,
pipelines: &WgpuPipelines,
factor: u32,
) {
let pipeline = match factor {
0 | 1 | 2 => &pipelines.tex_pipeline,
3 => &pipelines.tex_downsample_3x_pipeline,
_ => &pipelines.tex_downsample_4x_pipeline,
};
self.blit_to_inner(
device,
encoder,
target_view,
target_size,
dst_rect,
parent_clip,
pipeline,
pipelines,
);
}
#[allow(clippy::too_many_arguments)]
fn blit_to_inner(
&self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target_view: &wgpu::TextureView,
target_size: (u32, u32),
dst_rect: Rect,
parent_clip: Option<[i32; 4]>,
pipeline: &wgpu::RenderPipeline,
pipelines: &WgpuPipelines,
) {
let [gl_x, gl_y, gl_w, gl_h] = pixel_rect(dst_rect);
if gl_w <= 0 || gl_h <= 0 {
return;
}
let tex_uniforms = TexUniforms {
resolution: [target_size.0 as f32, target_size.1 as f32],
_pad: [0.0; 2],
tint: [1.0, 1.0, 1.0, 1.0],
};
let tex_ub = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("ssaa_blit_uniforms"),
contents: bytemuck::bytes_of(&tex_uniforms),
usage: wgpu::BufferUsages::UNIFORM,
});
let tex_bg0 = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("ssaa_blit_bg0"),
layout: &pipelines.tex_bgl0,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: tex_ub.as_entire_binding(),
}],
});
let tex_bg1 = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("ssaa_blit_bg1"),
layout: &pipelines.tex_bgl1,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.color.1),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.blit_sampler),
},
],
});
let x0 = gl_x as f32;
let y0 = gl_y as f32;
let x1 = x0 + gl_w as f32;
let y1 = y0 + gl_h as f32;
let verts: [f32; 24] = [
x0, y0, 0.0, 1.0, x1, y0, 1.0, 1.0, x1, y1, 1.0, 0.0, x0, y0, 0.0, 1.0, x1, y1, 1.0,
0.0, x0, y1, 0.0, 0.0,
];
let vb = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("ssaa_blit_vb"),
contents: bytemuck::cast_slice(&verts),
usage: wgpu::BufferUsages::VERTEX,
});
let scissor = parent_clip
.map(|[px, py, pw, ph]| {
let x1 = (gl_x + gl_w).min(px + pw);
let y1 = (gl_y + gl_h).min(py + ph);
let x0c = gl_x.max(px);
let y0c = gl_y.max(py);
let w = (x1 - x0c).max(0);
let h = (y1 - y0c).max(0);
let y_down = (target_size.1 as i32 - (y0c + h)).max(0);
(x0c.max(0) as u32, y_down as u32, w as u32, h as u32)
})
.unwrap_or_else(|| {
let y_down = (target_size.1 as i32 - (gl_y + gl_h)).max(0);
(gl_x.max(0) as u32, y_down as u32, gl_w as u32, gl_h as u32)
});
if scissor.2 == 0 || scissor.3 == 0 {
return;
}
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("ssaa_blit_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target_view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_viewport(
0.0,
0.0,
target_size.0 as f32,
target_size.1 as f32,
0.0,
1.0,
);
pass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
pass.set_pipeline(pipeline);
pass.set_bind_group(0, &tex_bg0, &[]);
pass.set_bind_group(1, &tex_bg1, &[]);
pass.set_vertex_buffer(0, vb.slice(..));
pass.draw(0..6, 0..1);
drop(pass);
let _ = (tex_ub, tex_bg0, tex_bg1, vb);
}
}
fn alloc_color(
device: &wgpu::Device,
w: u32,
h: u32,
format: wgpu::TextureFormat,
) -> (wgpu::Texture, wgpu::TextureView) {
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("ssaa_color"),
size: wgpu::Extent3d {
width: w,
height: h,
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
| wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
(tex, view)
}
fn alloc_depth(device: &wgpu::Device, w: u32, h: u32) -> (wgpu::Texture, wgpu::TextureView) {
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("ssaa_depth"),
size: wgpu::Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
(tex, view)
}
fn pixel_rect(rect: Rect) -> [i32; 4] {
let x0 = rect.x.floor() as i32;
let y0 = rect.y.floor() as i32;
let x1 = (rect.x + rect.width).ceil() as i32;
let y1 = (rect.y + rect.height).ceil() as i32;
[x0, y0, (x1 - x0).max(0), (y1 - y0).max(0)]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pixel_rect_covers_fractional_physical_extent() {
let rect = Rect::new(10.25, 20.5, 16.5, 10.25);
assert_eq!(pixel_rect(rect), [10, 20, 17, 11]);
}
#[test]
fn ssaa_linear_scale_matches_segments() {
assert_eq!(ssaa_linear_scale(0), 1);
assert_eq!(ssaa_linear_scale(1), 1);
assert_eq!(ssaa_linear_scale(4), 2);
assert_eq!(ssaa_linear_scale(9), 3);
assert_eq!(ssaa_linear_scale(16), 4);
assert_eq!(ssaa_linear_scale(7), 3);
assert_eq!(ssaa_linear_scale(100), 4);
}
}