use crate::kvasir::node::{ExecutionContext, KvasirNode};
use crate::kvasir::nodes::{PassId, RES_BLUR_A, RES_SCENE};
use crate::kvasir::resource::ResourceId;
pub struct BackdropCopyNode {
pub inputs: Vec<ResourceId>,
pub outputs: Vec<ResourceId>,
}
impl BackdropCopyNode {
pub fn new() -> Self {
Self {
inputs: vec![RES_SCENE],
outputs: vec![RES_BLUR_A],
}
}
}
impl KvasirNode for BackdropCopyNode {
fn label(&self) -> &'static str {
"Backdrop Copy"
}
fn inputs(&self) -> &[ResourceId] {
&self.inputs
}
fn outputs(&self) -> &[ResourceId] {
&self.outputs
}
fn pass_id(&self) -> PassId {
PassId::BackdropCopy
}
fn execute(&self, ctx: &mut ExecutionContext) {
let target_texture = match ctx.registry.get_texture(RES_BLUR_A) {
Some(v) => v,
None => {
log::error!("Missing texture for {}", stringify!(RES_BLUR_A));
return;
}
};
let target_view = {
let mut cache = ctx.renderer.texture_view_cache.lock().unwrap();
cache
.entry((RES_BLUR_A, 0))
.or_insert_with(|| {
target_texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("backdrop_copy_mip0"),
base_mip_level: 0,
mip_level_count: Some(1),
..Default::default()
})
})
.clone()
};
let mut p = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Surtr Backdrop Copy"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &target_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
}),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
..Default::default()
});
p.set_pipeline(&ctx.renderer.copy_pipeline);
let scene_view = match ctx.registry.get_texture_view(RES_SCENE) {
Some(v) => v,
None => {
log::error!("Missing texture view for {}", stringify!(RES_SCENE));
return;
}
};
let source_bind_group = {
let mut cache = ctx.renderer.bind_group_cache.lock().unwrap();
cache
.entry((RES_SCENE, 0, false))
.or_insert_with(|| {
ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("backdrop_copy_bg"),
layout: &ctx.renderer.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureViewArray(&vec![
&scene_view;
256
]),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(
&ctx.renderer.dummy_sampler,
),
},
],
})
})
.clone()
};
p.set_bind_group(0, &source_bind_group, &[]);
p.set_bind_group(1, &ctx.renderer.dummy_env_bind_group, &[]);
p.set_bind_group(2, &ctx.renderer.berserker_bind_group, &[]);
p.draw(0..3, 0..1);
}
}
pub struct BackdropBlurNode {
pub inputs: Vec<ResourceId>,
pub outputs: Vec<ResourceId>,
pub width: u32,
pub height: u32,
}
impl BackdropBlurNode {
pub fn new(width: u32, height: u32) -> Self {
Self {
inputs: vec![RES_BLUR_A],
outputs: vec![RES_BLUR_A],
width,
height,
}
}
}
impl KvasirNode for BackdropBlurNode {
fn label(&self) -> &'static str {
"Backdrop Blur"
}
fn inputs(&self) -> &[ResourceId] {
&self.inputs
}
fn outputs(&self) -> &[ResourceId] {
&self.outputs
}
fn pass_id(&self) -> PassId {
PassId::BackdropBlur
}
fn execute(&self, ctx: &mut ExecutionContext) {
let blur_tex = match ctx.registry.get_texture(RES_BLUR_A) {
Some(v) => v,
None => {
log::error!("Missing texture for {}", stringify!(RES_BLUR_A));
return;
}
};
let kawase_uniform = &ctx.renderer.kawase_uniform;
let num_mips = blur_tex.mip_level_count();
let effective_mips = (num_mips as usize).min(6);
if effective_mips < 2 {
return;
}
let mip_views: Vec<wgpu::TextureView> = (0..effective_mips)
.map(|mip| {
let mut cache = ctx.renderer.texture_view_cache.lock().unwrap();
cache
.entry((RES_BLUR_A, mip as u32))
.or_insert_with(|| {
blur_tex.create_view(&wgpu::TextureViewDescriptor {
label: Some(&format!("blur_mip_{}", mip)),
base_mip_level: mip as u32,
mip_level_count: Some(1),
..Default::default()
})
})
.clone()
})
.collect();
let blur_width = self.width;
let blur_height = self.height;
let mip_scales: Vec<(f32, f32, f32)> = (0..effective_mips)
.map(|i| {
let div = (1u32 << i) as f32;
(
blur_width as f32 / div,
blur_height as f32 / div,
(i + 1) as f32,
)
})
.collect();
for mip in 1..effective_mips {
let kernel_width = mip_scales[mip].2;
let uniform_data: [f32; 8] = [
mip_scales[(mip - 1)].0,
mip_scales[(mip - 1)].1,
(mip - 1) as f32,
kernel_width,
0.0,
0.0,
0.0,
0.0,
];
ctx.queue
.write_buffer(kawase_uniform, 0, bytemuck::cast_slice(&uniform_data[..8]));
let w = mip_scales[mip].0.max(1.0) as u32;
let h = mip_scales[mip].1.max(1.0) as u32;
let cache_key = (RES_BLUR_A, mip as u32, false);
let mut cache = ctx.renderer.bind_group_cache.lock().unwrap();
let bg = cache.entry(cache_key).or_insert_with(|| {
ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("kawase_bg_{}", mip)),
layout: &ctx.renderer.kawase_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: kawase_uniform,
offset: 0,
size: wgpu::BufferSize::new(32),
}),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&mip_views[(mip - 1)]),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&ctx.renderer.sampler),
},
],
})
});
let mut p = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some(&format!("Kawase Down {}", mip)),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &mip_views[mip],
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
}),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
..Default::default()
});
p.set_viewport(0.0, 0.0, w as f32, h as f32, 0.0, 1.0);
p.set_pipeline(&ctx.renderer.kawase_down_pipeline);
p.set_bind_group(0, Some(&*bg), &[]);
p.draw(0..3, 0..1);
}
for mip in (1..effective_mips).rev() {
let kernel_width = mip_scales[mip].2;
let uniform_data: [f32; 8] = [
mip_scales[mip].0,
mip_scales[mip].1,
mip as f32,
kernel_width,
0.0,
0.0,
0.0,
0.0,
];
ctx.queue
.write_buffer(kawase_uniform, 0, bytemuck::cast_slice(&uniform_data[..8]));
let w = mip_scales[(mip - 1)].0.max(1.0) as u32;
let h = mip_scales[(mip - 1)].1.max(1.0) as u32;
let bg = ctx.get_or_create_bind_group(
(RES_BLUR_A, mip as u32, true),
&ctx.renderer.kawase_bind_group_layout,
&[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: kawase_uniform,
offset: 0,
size: wgpu::BufferSize::new(32),
}),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&mip_views[mip]),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&ctx.renderer.sampler),
},
],
Some(&format!("kawase_up_bg_{}", mip)),
);
let mut p = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some(&format!("Kawase Up {}", mip)),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &mip_views[(mip - 1)],
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
..Default::default()
});
p.set_viewport(0.0, 0.0, w as f32, h as f32, 0.0, 1.0);
p.set_pipeline(&ctx.renderer.kawase_up_pipeline);
p.set_bind_group(0, Some(&bg), &[]);
p.draw(0..3, 0..1);
}
log::trace!(
"[Kvasir] backdrop_blur: Kawase pyramid ({}x{})",
blur_width,
blur_height
);
}
}
pub struct GlassNode {
pub inputs: Vec<ResourceId>,
pub outputs: Vec<ResourceId>,
pub scale: f32,
}
impl GlassNode {
pub fn new(scale: f32) -> Self {
Self {
inputs: vec![RES_SCENE, RES_BLUR_A],
outputs: vec![RES_SCENE],
scale,
}
}
}
impl KvasirNode for GlassNode {
fn label(&self) -> &'static str {
"Glass"
}
fn inputs(&self) -> &[ResourceId] {
&self.inputs
}
fn outputs(&self) -> &[ResourceId] {
&self.outputs
}
fn pass_id(&self) -> PassId {
PassId::Glass
}
fn execute(&self, ctx: &mut ExecutionContext) {
let rt_w = ctx.renderer.current_width() as i32;
let rt_h = ctx.renderer.current_height() as i32;
let scene_view = match ctx.registry.get_texture_view(RES_SCENE) {
Some(v) => v,
None => {
log::error!("Missing texture view for {}", stringify!(RES_SCENE));
return;
}
};
let msaa_view = match ctx
.registry
.get_texture_view(crate::kvasir::nodes::RES_SCENE_MSAA)
{
Some(v) => v,
None => {
log::error!(
"Missing texture view for {}",
stringify!(crate::kvasir::nodes::RES_SCENE_MSAA)
);
return;
}
};
let blur_view = match ctx.registry.get_texture_view(RES_BLUR_A) {
Some(v) => v,
None => {
log::error!("Missing texture view for {}", stringify!(RES_BLUR_A));
return;
}
};
let ctx_blur_env_bind_group_a = ctx.blur_env_bind_group_a;
let mut p = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Surtr P3 Liquid Glass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &scene_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
..Default::default()
});
p.set_pipeline(&ctx.renderer.glass_pipeline);
p.set_vertex_buffer(0, ctx.renderer.vertex_buffer.slice(..));
p.set_vertex_buffer(1, ctx.renderer.instance_buffer.slice(..));
p.set_index_buffer(
ctx.renderer.index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
p.set_bind_group(0, &ctx.renderer.dummy_texture_bind_group, &[]);
p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]);
p.set_bind_group(2, &ctx.renderer.berserker_bind_group, &[]);
let scale = self.scale;
for call in ctx
.renderer
.draw_calls
.iter()
.filter(|c| matches!(c.material, cvkg_core::DrawMaterial::Glass { .. }))
{
let mut portal_index = None;
if let Some(scissor) = call.scissor_rect {
for (idx, region) in ctx.renderer.portal_regions.iter().enumerate() {
let dx = (scissor.x - region.x).abs();
let dy = (scissor.y - region.y).abs();
let dw = (scissor.width - region.width).abs();
let dh = (scissor.height - region.height).abs();
if dx < 1.0 && dy < 1.0 && dw < 1.0 && dh < 1.0 {
portal_index = Some(idx);
break;
}
}
}
let env_bg = if let Some(portal_idx) = portal_index {
let portal_res_id = ResourceId(2000 + portal_idx as u32);
if let Some(portal_view) = ctx.registry.get_texture_view(portal_res_id) {
let cache_key = (portal_res_id, 0, false);
let mut cache = ctx.renderer.bind_group_cache.lock().unwrap();
let bg = cache.entry(cache_key).or_insert_with(|| {
ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("portal_blur_env_bg_{}", portal_idx)),
layout: &ctx.renderer.env_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&portal_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&ctx.renderer.sampler),
},
],
})
});
Some(bg.clone())
} else {
None
}
} else {
None
};
let active_env_bg = env_bg.as_ref().unwrap_or(ctx_blur_env_bind_group_a);
p.set_bind_group(1, active_env_bg, &[]);
let bg = if let Some(id) = call.texture_id {
if id == 0 {
&ctx.renderer.mega_heim_bind_group
} else {
ctx.renderer
.texture_bind_groups
.get(id as usize)
.unwrap_or(&ctx.renderer.dummy_texture_bind_group)
}
} else {
&ctx.renderer.dummy_texture_bind_group
};
p.set_bind_group(0, bg, &[]);
if let Some(rect) = call.scissor_rect
&& rt_w > 0
&& rt_h > 0
{
let x1 = (rect.x * scale).round() as i32;
let y1 = (rect.y * scale).round() as i32;
let x2 = ((rect.x + rect.width) * scale).round() as i32;
let y2 = ((rect.y + rect.height) * scale).round() as i32;
let w = (x2 - x1).clamp(0, rt_w);
let h = (y2 - y1).clamp(0, rt_h);
if w > 0 && h > 0 {
p.set_scissor_rect(x1 as u32, y1 as u32, w as u32, h as u32);
} else {
p.set_scissor_rect(0, 0, 1, 1);
}
} else {
p.set_scissor_rect(0, 0, rt_w as u32, rt_h as u32);
}
p.draw_indexed(
call.index_start..call.index_start + call.index_count,
0,
call.instance_start..call.instance_start + 1,
);
}
}
}