use super::blur_plan::{IDLE_FRAMES, PARAMS_SIZE, Scissor, plan};
use super::blur_setup::{fullscreen, params_slots};
use crate::gpu::instance_buffer::InstanceBuffer;
use crate::gpu::pipelines::Pipelines;
use crate::gpu::scene::Scene;
struct Targets {
size: (u32, u32),
scene: wgpu::TextureView,
low_a: wgpu::TextureView,
low_b: wgpu::TextureView,
scene_bind: wgpu::BindGroup,
a_bind: wgpu::BindGroup,
b_bind: wgpu::BindGroup,
}
pub(crate) struct BlurState {
pass: wgpu::RenderPipeline,
blit: wgpu::RenderPipeline,
composite: wgpu::RenderPipeline,
params_layout: wgpu::BindGroupLayout,
params: wgpu::Buffer,
params_bind: wgpu::BindGroup,
params_capacity: usize,
params_stride: u64,
sampler: wgpu::Sampler,
instances: InstanceBuffer,
format: wgpu::TextureFormat,
targets: Option<Targets>,
scissors: Vec<(Scissor, Scissor)>,
scratch: Vec<u8>,
idle: u32,
}
impl BlurState {
pub(crate) fn new(device: &wgpu::Device, pipelines: &Pipelines, format: wgpu::TextureFormat) -> Self {
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("aura_blur_shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("blur.wgsl").into()),
});
let params_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("aura_blur_params_layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: wgpu::BufferSize::new(PARAMS_SIZE),
},
count: None,
}],
});
let layout = |label, groups: &[Option<&wgpu::BindGroupLayout>]| {
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some(label),
bind_group_layouts: groups,
immediate_size: 0,
})
};
let pass_layout = layout("aura_blur_pass_layout", &[Some(&pipelines.atlas_layout), Some(¶ms_layout)]);
let blit_layout = layout("aura_blit_layout", &[Some(&pipelines.atlas_layout)]);
let composite = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("aura_blur_composite"),
layout: Some(&pipelines.layout),
vertex: wgpu::VertexState {
module: &pipelines.shader,
entry_point: Some("vs_blur"),
compilation_options: Default::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &pipelines.shader,
entry_point: Some("fs_blur"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleStrip, ..Default::default() },
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let params_stride =
PARAMS_SIZE.next_multiple_of(u64::from(device.limits().min_uniform_buffer_offset_alignment));
let (params, params_bind) = params_slots(device, ¶ms_layout, params_stride, 2);
Self {
pass: fullscreen(device, &module, &pass_layout, format, "fs_blur_pass"),
blit: fullscreen(device, &module, &blit_layout, format, "fs_blit"),
composite,
params_layout,
params,
params_bind,
params_capacity: 2,
params_stride,
sampler: device.create_sampler(&wgpu::SamplerDescriptor {
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
}),
instances: InstanceBuffer::new(device, &pipelines.instances_layout, "aura_blurs"),
format,
targets: None,
scissors: Vec::new(),
scratch: Vec::new(),
idle: 0,
}
}
fn make_targets(&self, device: &wgpu::Device, pipelines: &Pipelines, size: (u32, u32)) -> Targets {
let texture = |label, (width, height): (u32, u32)| {
device
.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: self.format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
})
.create_view(&wgpu::TextureViewDescriptor::default())
};
let low = (size.0.div_ceil(2).max(1), size.1.div_ceil(2).max(1));
let (scene, low_a, low_b) =
(texture("aura_blur_scene", size), texture("aura_blur_a", low), texture("aura_blur_b", low));
let bind = |view: &wgpu::TextureView| {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("aura_blur_bind"),
layout: &pipelines.atlas_layout,
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(view) },
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&self.sampler) },
],
})
};
Targets { size, scene_bind: bind(&scene), a_bind: bind(&low_a), b_bind: bind(&low_b), scene, low_a, low_b }
}
pub(crate) fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
pipelines: &Pipelines,
scene: &Scene,
size: (u32, u32),
scale: f32,
) {
self.idle = 0;
if self.targets.as_ref().is_none_or(|targets| targets.size != size) {
self.targets = Some(self.make_targets(device, pipelines, size));
}
self.instances.upload(device, queue, &pipelines.instances_layout, &scene.blurs);
let slots = scene.blurs.len() * 2;
if slots > self.params_capacity {
self.params_capacity = slots.next_power_of_two();
(self.params, self.params_bind) =
params_slots(device, &self.params_layout, self.params_stride, self.params_capacity);
}
let low = (size.0.div_ceil(2).max(1), size.1.div_ceil(2).max(1));
self.scissors.clear();
self.scratch.clear();
self.scratch.resize(slots * self.params_stride as usize, 0);
for (index, blur) in scene.blurs.iter().enumerate() {
let (first, second, params) = plan(blur, scale, low);
self.scissors.push((first, second));
for (pass, params) in params.iter().enumerate() {
let start = (index * 2 + pass) * self.params_stride as usize;
self.scratch[start..start + PARAMS_SIZE as usize].copy_from_slice(bytemuck::bytes_of(params));
}
}
queue.write_buffer(&self.params, 0, &self.scratch);
}
pub(crate) fn idle_frame(&mut self) {
self.idle += 1;
if self.idle > IDLE_FRAMES {
self.targets = None;
}
}
pub(crate) fn release(&mut self) {
self.targets = None;
}
pub(crate) fn instances(&self) -> &InstanceBuffer {
&self.instances
}
pub(crate) fn composite(&self) -> &wgpu::RenderPipeline {
&self.composite
}
pub(crate) fn blurred_bind(&self) -> &wgpu::BindGroup {
&self.targets.as_ref().expect("prepare crea las texturas").b_bind
}
pub(crate) fn scene_view(&self) -> &wgpu::TextureView {
&self.targets.as_ref().expect("prepare crea las texturas").scene
}
pub(crate) fn blur_passes(&self, encoder: &mut wgpu::CommandEncoder, index: usize) {
let Some(targets) = &self.targets else { return };
let (first, second) = self.scissors[index];
for (pass, (scissor, source, destination)) in
[(first, &targets.scene_bind, &targets.low_a), (second, &targets.a_bind, &targets.low_b)]
.into_iter()
.enumerate()
{
let Some([x, y, width, height]) = scissor else { continue };
let mut render = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("aura_blur_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: destination,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
..Default::default()
});
render.set_pipeline(&self.pass);
render.set_bind_group(0, source, &[]);
render.set_bind_group(1, &self.params_bind, &[((index * 2 + pass) as u64 * self.params_stride) as u32]);
render.set_scissor_rect(x, y, width, height);
render.draw(0..3, 0..1);
}
}
pub(crate) fn blit(&self, encoder: &mut wgpu::CommandEncoder, view: &wgpu::TextureView) {
let Some(targets) = &self.targets else { return };
let mut render = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("aura_blit"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store },
})],
..Default::default()
});
render.set_pipeline(&self.blit);
render.set_bind_group(0, &targets.scene_bind, &[]);
render.draw(0..3, 0..1);
}
}