Replace the blit cascade in record_blur with shader-based dual-Kawase,
matching the OpenGL backend's algorithm. The blit cascade was a much weaker filter that degenerated badly on thin surfaces. The new path adds blur.vert / blur_down.frag (5-tap) / blur_up.frag (8-tap), a single-binding blur descriptor set layout, and per-format down/up pipelines. BLUR_SCRATCH_USAGE gains COLOR_ATTACHMENT so the scratch images can be both sampled and rendered into. Cache hit fast path and masked composite are unchanged.
This commit is contained in:
parent
fa0ab2f1fa
commit
e2de688324
12 changed files with 707 additions and 176 deletions
|
|
@ -25,6 +25,9 @@ pub const TREES: &[Tree] = &[Tree {
|
||||||
"rounded_tex.vert",
|
"rounded_tex.vert",
|
||||||
"blur_composite.vert",
|
"blur_composite.vert",
|
||||||
"blur_composite.frag",
|
"blur_composite.frag",
|
||||||
|
"blur.vert",
|
||||||
|
"blur_down.frag",
|
||||||
|
"blur_up.frag",
|
||||||
"legacy/fill.frag",
|
"legacy/fill.frag",
|
||||||
"legacy/fill.vert",
|
"legacy/fill.vert",
|
||||||
"legacy/tex.vert",
|
"legacy/tex.vert",
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ use {
|
||||||
crate::gfx_apis::vulkan::{
|
crate::gfx_apis::vulkan::{
|
||||||
VulkanError,
|
VulkanError,
|
||||||
image::{QueueFamily, QueueState, VulkanImage, VulkanImageMemory},
|
image::{QueueFamily, QueueState, VulkanImage, VulkanImageMemory},
|
||||||
|
pipeline::VulkanPipeline,
|
||||||
renderer::VulkanRenderer,
|
renderer::VulkanRenderer,
|
||||||
shaders::BlurCompositePushConstants,
|
shaders::{BlurCompositePushConstants, BlurPushConstants},
|
||||||
},
|
},
|
||||||
ash::vk::{
|
ash::vk::{
|
||||||
AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BlitImageInfo2, CommandBuffer,
|
AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BlitImageInfo2, CommandBuffer,
|
||||||
|
|
@ -16,13 +17,14 @@ use {
|
||||||
},
|
},
|
||||||
gpu_alloc::UsageFlags,
|
gpu_alloc::UsageFlags,
|
||||||
run_on_drop::on_drop,
|
run_on_drop::on_drop,
|
||||||
std::{cell::Cell, collections::hash_map::Entry, rc::Rc, slice},
|
std::{cell::Cell, rc::Rc, slice},
|
||||||
};
|
};
|
||||||
|
|
||||||
const BLUR_SCRATCH_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw(
|
const BLUR_SCRATCH_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw(
|
||||||
ImageUsageFlags::TRANSFER_SRC.as_raw()
|
ImageUsageFlags::TRANSFER_SRC.as_raw()
|
||||||
| ImageUsageFlags::TRANSFER_DST.as_raw()
|
| ImageUsageFlags::TRANSFER_DST.as_raw()
|
||||||
| ImageUsageFlags::SAMPLED.as_raw(),
|
| ImageUsageFlags::SAMPLED.as_raw()
|
||||||
|
| ImageUsageFlags::COLOR_ATTACHMENT.as_raw(),
|
||||||
);
|
);
|
||||||
|
|
||||||
pub(super) struct BlurMaskRecord<'a> {
|
pub(super) struct BlurMaskRecord<'a> {
|
||||||
|
|
@ -42,12 +44,17 @@ impl VulkanRenderer {
|
||||||
) -> Result<Rc<VulkanImage>, VulkanError> {
|
) -> Result<Rc<VulkanImage>, VulkanError> {
|
||||||
let key = (width, height, format);
|
let key = (width, height, format);
|
||||||
let cached = &mut *self.blur_scratch.borrow_mut();
|
let cached = &mut *self.blur_scratch.borrow_mut();
|
||||||
let entry = cached.entry(key);
|
|
||||||
if let Entry::Occupied(e) = &entry
|
if let Some(weak) = cached.get(&key) {
|
||||||
&& let Some(img) = e.get().upgrade()
|
if let Some(img) = weak.upgrade() {
|
||||||
{
|
if Rc::strong_count(&img) == 1 {
|
||||||
|
img.is_undefined.set(false);
|
||||||
|
img.contents_are_undefined.set(false);
|
||||||
return Ok(img);
|
return Ok(img);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let create_info = ImageCreateInfo::default()
|
let create_info = ImageCreateInfo::default()
|
||||||
.image_type(ImageType::TYPE_2D)
|
.image_type(ImageType::TYPE_2D)
|
||||||
.format(format)
|
.format(format)
|
||||||
|
|
@ -77,7 +84,6 @@ impl VulkanRenderer {
|
||||||
.bind_image_memory(image, allocation.memory, allocation.offset)
|
.bind_image_memory(image, allocation.memory, allocation.offset)
|
||||||
};
|
};
|
||||||
res.map_err(VulkanError::BindImageMemory)?;
|
res.map_err(VulkanError::BindImageMemory)?;
|
||||||
// No view needed (we only blit), but VulkanImage requires one.
|
|
||||||
let image_view_create_info = ImageViewCreateInfo::default()
|
let image_view_create_info = ImageViewCreateInfo::default()
|
||||||
.image(image)
|
.image(image)
|
||||||
.format(format)
|
.format(format)
|
||||||
|
|
@ -96,8 +102,6 @@ impl VulkanRenderer {
|
||||||
};
|
};
|
||||||
let view = view.map_err(VulkanError::CreateImageView)?;
|
let view = view.map_err(VulkanError::CreateImageView)?;
|
||||||
destroy_image.forget();
|
destroy_image.forget();
|
||||||
// Reuse the BLEND_FORMAT placeholder; the format field is informational
|
|
||||||
// here, blit ops use the actual VkFormat above.
|
|
||||||
let img = Rc::new(VulkanImage {
|
let img = Rc::new(VulkanImage {
|
||||||
renderer: self.clone(),
|
renderer: self.clone(),
|
||||||
format: crate::gfx_apis::vulkan::format::BLEND_FORMAT,
|
format: crate::gfx_apis::vulkan::format::BLEND_FORMAT,
|
||||||
|
|
@ -117,28 +121,30 @@ impl VulkanRenderer {
|
||||||
sampled_image_descriptor: None,
|
sampled_image_descriptor: None,
|
||||||
execution_version: Default::default(),
|
execution_version: Default::default(),
|
||||||
});
|
});
|
||||||
match entry {
|
cached.insert(key, Rc::downgrade(&img));
|
||||||
Entry::Occupied(mut e) => {
|
|
||||||
e.insert(Rc::downgrade(&img));
|
|
||||||
}
|
|
||||||
Entry::Vacant(e) => {
|
|
||||||
e.insert(Rc::downgrade(&img));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(img)
|
Ok(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Records a backdrop blur of the given pixel rect on the target image.
|
/// Records a dual-Kawase backdrop blur of the given pixel rect on the target
|
||||||
/// Caller is responsible for ending the current dynamic render pass before
|
/// image. Caller is responsible for ending the current dynamic render pass
|
||||||
/// invoking, and for restarting it afterward (with LOAD).
|
/// before invoking, and for restarting it afterward (with LOAD).
|
||||||
|
///
|
||||||
|
/// If `cached_blur` is Some, the cascade is skipped and that image is used
|
||||||
|
/// directly as the blurred input to the composite. The mask must also be Some
|
||||||
|
/// in that case, since cache+no-mask is just a no-op (blit-back of cached).
|
||||||
|
/// On a cache miss, the level-0 scratch image (holding the blurred result)
|
||||||
|
/// is returned via `out_blur_image` for the caller to store in the cache.
|
||||||
pub(super) fn record_blur(
|
pub(super) fn record_blur(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
buf: CommandBuffer,
|
buf: CommandBuffer,
|
||||||
target: &VulkanImage,
|
target: &VulkanImage,
|
||||||
rect: [i32; 4],
|
rect: [i32; 4],
|
||||||
passes: u8,
|
passes: u8,
|
||||||
|
offset: f32,
|
||||||
scratch_out: &mut Vec<Rc<VulkanImage>>,
|
scratch_out: &mut Vec<Rc<VulkanImage>>,
|
||||||
mask: Option<&BlurMaskRecord<'_>>,
|
mask: Option<&BlurMaskRecord<'_>>,
|
||||||
|
cached_blur: Option<&Rc<VulkanImage>>,
|
||||||
|
out_blur_image: &mut Option<Rc<VulkanImage>>,
|
||||||
) -> Result<(), VulkanError> {
|
) -> Result<(), VulkanError> {
|
||||||
let [x1, y1, x2, y2] = rect;
|
let [x1, y1, x2, y2] = rect;
|
||||||
let x1 = x1.max(0).min(target.width as i32);
|
let x1 = x1.max(0).min(target.width as i32);
|
||||||
|
|
@ -150,7 +156,26 @@ impl VulkanRenderer {
|
||||||
if w < 4 || h < 4 {
|
if w < 4 || h < 4 {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let passes = passes.clamp(1, 6) as u32;
|
let passes = passes.clamp(1, 8) as u32;
|
||||||
|
let offset = offset.max(0.0);
|
||||||
|
|
||||||
|
let dev = &self.device.device;
|
||||||
|
|
||||||
|
// Cache hit fast path: skip cascade, just composite from cached image.
|
||||||
|
// The format check matters because BlurBarrier may run in either the
|
||||||
|
// BlendBuffer pass (linear format) or the FrameBuffer pass (gamma)
|
||||||
|
// depending on whether BB was elided. A cached image from one pass
|
||||||
|
// has the wrong format for the other.
|
||||||
|
if let (Some(cached), Some(mask)) = (cached_blur, mask)
|
||||||
|
&& cached.width == w
|
||||||
|
&& cached.height == h
|
||||||
|
&& cached.format.vk_format == target.format.vk_format
|
||||||
|
{
|
||||||
|
self.record_blur_composite_only(buf, target, cached, mask, [x1, y1, x2, y2])?;
|
||||||
|
// Hold cached image alive for this frame's GPU execution.
|
||||||
|
scratch_out.push(cached.clone());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let format = target.format.vk_format;
|
let format = target.format.vk_format;
|
||||||
let mut levels: Vec<Rc<VulkanImage>> = Vec::with_capacity(passes as usize + 1);
|
let mut levels: Vec<Rc<VulkanImage>> = Vec::with_capacity(passes as usize + 1);
|
||||||
|
|
@ -163,7 +188,8 @@ impl VulkanRenderer {
|
||||||
levels.push(self.acquire_blur_scratch(cw, ch, format)?);
|
levels.push(self.acquire_blur_scratch(cw, ch, format)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let dev = &self.device.device;
|
// After cascade, levels[0] holds the blurred result. Stash it for caching.
|
||||||
|
*out_blur_image = Some(levels[0].clone());
|
||||||
let subres = ImageSubresourceLayers::default()
|
let subres = ImageSubresourceLayers::default()
|
||||||
.aspect_mask(ImageAspectFlags::COLOR)
|
.aspect_mask(ImageAspectFlags::COLOR)
|
||||||
.layer_count(1)
|
.layer_count(1)
|
||||||
|
|
@ -203,7 +229,7 @@ impl VulkanRenderer {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 1: target COLOR_ATTACHMENT -> TRANSFER_SRC.
|
// Step 1: target COLOR_ATTACHMENT -> TRANSFER_SRC.
|
||||||
// Step 1: levels[0] UNDEFINED -> TRANSFER_DST.
|
// levels[0] -> TRANSFER_DST (discard prior contents).
|
||||||
do_barriers(&[
|
do_barriers(&[
|
||||||
barrier(
|
barrier(
|
||||||
target.image,
|
target.image,
|
||||||
|
|
@ -224,6 +250,7 @@ impl VulkanRenderer {
|
||||||
AccessFlags2::TRANSFER_WRITE,
|
AccessFlags2::TRANSFER_WRITE,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
levels[0].is_undefined.set(false);
|
||||||
|
|
||||||
// Step 2: blit target rect -> levels[0] full.
|
// Step 2: blit target rect -> levels[0] full.
|
||||||
let blit = ImageBlit2::default()
|
let blit = ImageBlit2::default()
|
||||||
|
|
@ -252,121 +279,9 @@ impl VulkanRenderer {
|
||||||
dev.cmd_blit_image2(buf, &blit_info);
|
dev.cmd_blit_image2(buf, &blit_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Down passes: levels[i-1] -> levels[i] with linear filter.
|
// Step 3: levels[0] TRANSFER_DST -> SHADER_READ_ONLY for sampling in
|
||||||
for i in 1..=passes as usize {
|
// the down pass.
|
||||||
let (src, dst) = (&levels[i - 1], &levels[i]);
|
do_barriers(&[barrier(
|
||||||
do_barriers(&[
|
|
||||||
barrier(
|
|
||||||
src.image,
|
|
||||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
||||||
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
|
||||||
PipelineStageFlags2::TRANSFER,
|
|
||||||
AccessFlags2::TRANSFER_WRITE,
|
|
||||||
PipelineStageFlags2::TRANSFER,
|
|
||||||
AccessFlags2::TRANSFER_READ,
|
|
||||||
),
|
|
||||||
barrier(
|
|
||||||
dst.image,
|
|
||||||
ImageLayout::UNDEFINED,
|
|
||||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
||||||
PipelineStageFlags2::TOP_OF_PIPE,
|
|
||||||
AccessFlags2::empty(),
|
|
||||||
PipelineStageFlags2::TRANSFER,
|
|
||||||
AccessFlags2::TRANSFER_WRITE,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
let blit = ImageBlit2::default()
|
|
||||||
.src_subresource(subres)
|
|
||||||
.dst_subresource(subres)
|
|
||||||
.src_offsets([
|
|
||||||
Offset3D { x: 0, y: 0, z: 0 },
|
|
||||||
Offset3D {
|
|
||||||
x: src.width as i32,
|
|
||||||
y: src.height as i32,
|
|
||||||
z: 1,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.dst_offsets([
|
|
||||||
Offset3D { x: 0, y: 0, z: 0 },
|
|
||||||
Offset3D {
|
|
||||||
x: dst.width as i32,
|
|
||||||
y: dst.height as i32,
|
|
||||||
z: 1,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
let blit_info = BlitImageInfo2::default()
|
|
||||||
.src_image(src.image)
|
|
||||||
.src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL)
|
|
||||||
.dst_image(dst.image)
|
|
||||||
.dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL)
|
|
||||||
.filter(Filter::LINEAR)
|
|
||||||
.regions(slice::from_ref(&blit));
|
|
||||||
unsafe {
|
|
||||||
dev.cmd_blit_image2(buf, &blit_info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Up passes: levels[i+1] -> levels[i] with linear filter.
|
|
||||||
for i in (0..passes as usize).rev() {
|
|
||||||
let (src, dst) = (&levels[i + 1], &levels[i]);
|
|
||||||
do_barriers(&[
|
|
||||||
barrier(
|
|
||||||
src.image,
|
|
||||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
||||||
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
|
||||||
PipelineStageFlags2::TRANSFER,
|
|
||||||
AccessFlags2::TRANSFER_WRITE,
|
|
||||||
PipelineStageFlags2::TRANSFER,
|
|
||||||
AccessFlags2::TRANSFER_READ,
|
|
||||||
),
|
|
||||||
barrier(
|
|
||||||
dst.image,
|
|
||||||
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
|
||||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
||||||
PipelineStageFlags2::TRANSFER,
|
|
||||||
AccessFlags2::TRANSFER_READ,
|
|
||||||
PipelineStageFlags2::TRANSFER,
|
|
||||||
AccessFlags2::TRANSFER_WRITE,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
let blit = ImageBlit2::default()
|
|
||||||
.src_subresource(subres)
|
|
||||||
.dst_subresource(subres)
|
|
||||||
.src_offsets([
|
|
||||||
Offset3D { x: 0, y: 0, z: 0 },
|
|
||||||
Offset3D {
|
|
||||||
x: src.width as i32,
|
|
||||||
y: src.height as i32,
|
|
||||||
z: 1,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.dst_offsets([
|
|
||||||
Offset3D { x: 0, y: 0, z: 0 },
|
|
||||||
Offset3D {
|
|
||||||
x: dst.width as i32,
|
|
||||||
y: dst.height as i32,
|
|
||||||
z: 1,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
let blit_info = BlitImageInfo2::default()
|
|
||||||
.src_image(src.image)
|
|
||||||
.src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL)
|
|
||||||
.dst_image(dst.image)
|
|
||||||
.dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL)
|
|
||||||
.filter(Filter::LINEAR)
|
|
||||||
.regions(slice::from_ref(&blit));
|
|
||||||
unsafe {
|
|
||||||
dev.cmd_blit_image2(buf, &blit_info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mask) = mask {
|
|
||||||
// Masked composite path:
|
|
||||||
// levels[0] (TRANSFER_DST) -> SHADER_READ_ONLY_OPTIMAL
|
|
||||||
// target (TRANSFER_SRC) -> COLOR_ATTACHMENT_OPTIMAL
|
|
||||||
// draw composite shader sampling levels[0] + mask, blending onto fb
|
|
||||||
do_barriers(&[
|
|
||||||
barrier(
|
|
||||||
levels[0].image,
|
levels[0].image,
|
||||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||||
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
|
@ -374,8 +289,135 @@ impl VulkanRenderer {
|
||||||
AccessFlags2::TRANSFER_WRITE,
|
AccessFlags2::TRANSFER_WRITE,
|
||||||
PipelineStageFlags2::FRAGMENT_SHADER,
|
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||||
AccessFlags2::SHADER_SAMPLED_READ,
|
AccessFlags2::SHADER_SAMPLED_READ,
|
||||||
),
|
)]);
|
||||||
barrier(
|
|
||||||
|
let blur_down_pipeline = self.get_or_create_blur_down_pipeline(format)?;
|
||||||
|
let blur_up_pipeline = self.get_or_create_blur_up_pipeline(format)?;
|
||||||
|
|
||||||
|
// Helper to run one blur pass: sample `src`, draw into `dst`. Caller
|
||||||
|
// must have transitioned dst to COLOR_ATTACHMENT and src to
|
||||||
|
// SHADER_READ_ONLY before this. Layouts after: dst stays in
|
||||||
|
// COLOR_ATTACHMENT (caller transitions next).
|
||||||
|
let run_pass = |pipeline: &VulkanPipeline,
|
||||||
|
src: &VulkanImage,
|
||||||
|
dst: &VulkanImage|
|
||||||
|
-> Result<(), VulkanError> {
|
||||||
|
let color_attachment = RenderingAttachmentInfo::default()
|
||||||
|
.image_view(dst.texture_view)
|
||||||
|
.image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||||
|
.load_op(AttachmentLoadOp::DONT_CARE)
|
||||||
|
.store_op(AttachmentStoreOp::STORE);
|
||||||
|
let render_area = Rect2D {
|
||||||
|
offset: Offset2D { x: 0, y: 0 },
|
||||||
|
extent: Extent2D {
|
||||||
|
width: dst.width,
|
||||||
|
height: dst.height,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let rendering_info = RenderingInfo::default()
|
||||||
|
.render_area(render_area)
|
||||||
|
.layer_count(1)
|
||||||
|
.color_attachments(slice::from_ref(&color_attachment));
|
||||||
|
let viewport = Viewport {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
width: dst.width as f32,
|
||||||
|
height: dst.height as f32,
|
||||||
|
min_depth: 0.0,
|
||||||
|
max_depth: 1.0,
|
||||||
|
};
|
||||||
|
let scissor = render_area;
|
||||||
|
let push = BlurPushConstants {
|
||||||
|
halfpixel: [0.5 / src.width as f32, 0.5 / src.height as f32],
|
||||||
|
offset,
|
||||||
|
};
|
||||||
|
let src_image_info = DescriptorImageInfo::default()
|
||||||
|
.image_view(src.texture_view)
|
||||||
|
.image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
let writes = [WriteDescriptorSet::default()
|
||||||
|
.dst_binding(0)
|
||||||
|
.descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||||
|
.image_info(slice::from_ref(&src_image_info))];
|
||||||
|
unsafe {
|
||||||
|
dev.cmd_begin_rendering(buf, &rendering_info);
|
||||||
|
dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline);
|
||||||
|
dev.cmd_set_viewport(buf, 0, slice::from_ref(&viewport));
|
||||||
|
dev.cmd_set_scissor(buf, 0, slice::from_ref(&scissor));
|
||||||
|
self.device.push_descriptor.cmd_push_descriptor_set(
|
||||||
|
buf,
|
||||||
|
PipelineBindPoint::GRAPHICS,
|
||||||
|
pipeline.pipeline_layout,
|
||||||
|
0,
|
||||||
|
&writes,
|
||||||
|
);
|
||||||
|
dev.cmd_push_constants(
|
||||||
|
buf,
|
||||||
|
pipeline.pipeline_layout,
|
||||||
|
ShaderStageFlags::FRAGMENT,
|
||||||
|
0,
|
||||||
|
uapi::as_bytes(&push),
|
||||||
|
);
|
||||||
|
dev.cmd_draw(buf, 4, 1, 0, 0);
|
||||||
|
dev.cmd_end_rendering(buf);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Down passes: levels[i-1] (SHADER_READ_ONLY) -> levels[i] (COLOR_ATT).
|
||||||
|
// Each iteration transitions the destination to COLOR_ATTACHMENT,
|
||||||
|
// draws, then to SHADER_READ_ONLY for the next iteration's read.
|
||||||
|
for i in 1..=passes as usize {
|
||||||
|
do_barriers(&[barrier(
|
||||||
|
levels[i].image,
|
||||||
|
ImageLayout::UNDEFINED,
|
||||||
|
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
PipelineStageFlags2::TOP_OF_PIPE,
|
||||||
|
AccessFlags2::empty(),
|
||||||
|
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||||
|
AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
||||||
|
)]);
|
||||||
|
levels[i].is_undefined.set(false);
|
||||||
|
run_pass(&blur_down_pipeline, &levels[i - 1], &levels[i])?;
|
||||||
|
do_barriers(&[barrier(
|
||||||
|
levels[i].image,
|
||||||
|
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||||
|
AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
||||||
|
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||||
|
AccessFlags2::SHADER_SAMPLED_READ,
|
||||||
|
)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Up passes: levels[i+1] (SHADER_READ_ONLY) -> levels[i] (COLOR_ATT).
|
||||||
|
for i in (0..passes as usize).rev() {
|
||||||
|
do_barriers(&[barrier(
|
||||||
|
levels[i].image,
|
||||||
|
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||||
|
AccessFlags2::SHADER_SAMPLED_READ,
|
||||||
|
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||||
|
AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
||||||
|
)]);
|
||||||
|
run_pass(&blur_up_pipeline, &levels[i + 1], &levels[i])?;
|
||||||
|
do_barriers(&[barrier(
|
||||||
|
levels[i].image,
|
||||||
|
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||||
|
AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
||||||
|
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||||
|
AccessFlags2::SHADER_SAMPLED_READ,
|
||||||
|
)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// After cascade: levels[0] in SHADER_READ_ONLY, target in TRANSFER_SRC.
|
||||||
|
|
||||||
|
if let Some(mask) = mask {
|
||||||
|
// Masked composite path: restore target to COLOR_ATTACHMENT and
|
||||||
|
// draw the composite shader sampling levels[0] + mask.
|
||||||
|
do_barriers(&[barrier(
|
||||||
target.image,
|
target.image,
|
||||||
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||||
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
|
@ -383,8 +425,7 @@ impl VulkanRenderer {
|
||||||
AccessFlags2::TRANSFER_READ,
|
AccessFlags2::TRANSFER_READ,
|
||||||
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||||
AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ,
|
AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ,
|
||||||
),
|
)]);
|
||||||
]);
|
|
||||||
|
|
||||||
let pipeline = self.get_or_create_blur_composite_pipeline(target.format.vk_format)?;
|
let pipeline = self.get_or_create_blur_composite_pipeline(target.format.vk_format)?;
|
||||||
|
|
||||||
|
|
@ -470,14 +511,15 @@ impl VulkanRenderer {
|
||||||
dev.cmd_end_rendering(buf);
|
dev.cmd_end_rendering(buf);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Final blit: levels[0] -> target rect.
|
// Unmasked: transition levels[0] back to TRANSFER_SRC, target stays
|
||||||
|
// in TRANSFER_SRC, retarget target to TRANSFER_DST, blit-back.
|
||||||
do_barriers(&[
|
do_barriers(&[
|
||||||
barrier(
|
barrier(
|
||||||
levels[0].image,
|
levels[0].image,
|
||||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||||
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||||
PipelineStageFlags2::TRANSFER,
|
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||||
AccessFlags2::TRANSFER_WRITE,
|
AccessFlags2::SHADER_SAMPLED_READ,
|
||||||
PipelineStageFlags2::TRANSFER,
|
PipelineStageFlags2::TRANSFER,
|
||||||
AccessFlags2::TRANSFER_READ,
|
AccessFlags2::TRANSFER_READ,
|
||||||
),
|
),
|
||||||
|
|
@ -516,9 +558,11 @@ impl VulkanRenderer {
|
||||||
unsafe {
|
unsafe {
|
||||||
dev.cmd_blit_image2(buf, &blit_info);
|
dev.cmd_blit_image2(buf, &blit_info);
|
||||||
}
|
}
|
||||||
|
// Restore target to COLOR_ATTACHMENT for the resumed render pass.
|
||||||
// Restore target to COLOR_ATTACHMENT_OPTIMAL.
|
// Also push levels[0] back to SHADER_READ_ONLY so its tracked layout
|
||||||
do_barriers(&[barrier(
|
// matches what the cache-hit fast path expects on next frame.
|
||||||
|
do_barriers(&[
|
||||||
|
barrier(
|
||||||
target.image,
|
target.image,
|
||||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||||
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
|
@ -526,11 +570,146 @@ impl VulkanRenderer {
|
||||||
AccessFlags2::TRANSFER_WRITE,
|
AccessFlags2::TRANSFER_WRITE,
|
||||||
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||||
AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ,
|
AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ,
|
||||||
)]);
|
),
|
||||||
|
barrier(
|
||||||
|
levels[0].image,
|
||||||
|
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||||
|
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
PipelineStageFlags2::TRANSFER,
|
||||||
|
AccessFlags2::TRANSFER_READ,
|
||||||
|
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||||
|
AccessFlags2::SHADER_SAMPLED_READ,
|
||||||
|
),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hold the scratch images until the frame is submitted.
|
// Hold the scratch images until the frame is submitted.
|
||||||
scratch_out.extend(levels);
|
scratch_out.extend(levels);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cache-hit fast path. Cached image is already in SHADER_READ_ONLY_OPTIMAL
|
||||||
|
/// (the layout we leave it in after the previous frame's composite). We just
|
||||||
|
/// re-bind the composite pipeline with the cached image as input and draw.
|
||||||
|
fn record_blur_composite_only(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
buf: CommandBuffer,
|
||||||
|
target: &VulkanImage,
|
||||||
|
cached: &Rc<VulkanImage>,
|
||||||
|
mask: &BlurMaskRecord<'_>,
|
||||||
|
rect: [i32; 4],
|
||||||
|
) -> Result<(), VulkanError> {
|
||||||
|
let [x1, y1, x2, y2] = rect;
|
||||||
|
let w = (x2 - x1) as u32;
|
||||||
|
let h = (y2 - y1) as u32;
|
||||||
|
let dev = &self.device.device;
|
||||||
|
// The caller (BlurBarrier handler) has already cmd_end_rendering'd the
|
||||||
|
// pass that produced the underlying scene. Without an explicit barrier
|
||||||
|
// here, the new render pass's LOAD reads can race with those prior
|
||||||
|
// COLOR_ATTACHMENT_WRITEs and pull stale memory — which manifests as
|
||||||
|
// per-frame flicker in the blurred region.
|
||||||
|
let target_load_barrier = ImageMemoryBarrier2::default()
|
||||||
|
.image(target.image)
|
||||||
|
.old_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||||
|
.new_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||||
|
.subresource_range(ImageSubresourceRange {
|
||||||
|
aspect_mask: ImageAspectFlags::COLOR,
|
||||||
|
base_mip_level: 0,
|
||||||
|
level_count: 1,
|
||||||
|
base_array_layer: 0,
|
||||||
|
layer_count: 1,
|
||||||
|
})
|
||||||
|
.src_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)
|
||||||
|
.src_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE)
|
||||||
|
.dst_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)
|
||||||
|
.dst_access_mask(
|
||||||
|
AccessFlags2::COLOR_ATTACHMENT_READ | AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
||||||
|
)
|
||||||
|
.src_queue_family_index(ash::vk::QUEUE_FAMILY_IGNORED)
|
||||||
|
.dst_queue_family_index(ash::vk::QUEUE_FAMILY_IGNORED);
|
||||||
|
let dep = DependencyInfoKHR::default()
|
||||||
|
.image_memory_barriers(slice::from_ref(&target_load_barrier));
|
||||||
|
unsafe {
|
||||||
|
dev.cmd_pipeline_barrier2(buf, &dep);
|
||||||
|
}
|
||||||
|
let pipeline = self.get_or_create_blur_composite_pipeline(target.format.vk_format)?;
|
||||||
|
let target_render_view = target.render_view.unwrap_or(target.texture_view);
|
||||||
|
let color_attachment = RenderingAttachmentInfo::default()
|
||||||
|
.image_view(target_render_view)
|
||||||
|
.image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||||
|
.load_op(AttachmentLoadOp::LOAD)
|
||||||
|
.store_op(AttachmentStoreOp::STORE);
|
||||||
|
let render_area = Rect2D {
|
||||||
|
offset: Offset2D { x: 0, y: 0 },
|
||||||
|
extent: Extent2D {
|
||||||
|
width: target.width,
|
||||||
|
height: target.height,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let rendering_info = RenderingInfo::default()
|
||||||
|
.render_area(render_area)
|
||||||
|
.layer_count(1)
|
||||||
|
.color_attachments(slice::from_ref(&color_attachment));
|
||||||
|
let viewport = Viewport {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
width: target.width as f32,
|
||||||
|
height: target.height as f32,
|
||||||
|
min_depth: 0.0,
|
||||||
|
max_depth: 1.0,
|
||||||
|
};
|
||||||
|
let scissor = Rect2D {
|
||||||
|
offset: Offset2D { x: x1, y: y1 },
|
||||||
|
extent: Extent2D {
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let blurred_tc: [[f32; 2]; 4] = [[1.0, 0.0], [0.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
|
||||||
|
let push = BlurCompositePushConstants {
|
||||||
|
pos: mask.target_points,
|
||||||
|
blurred_tex_pos: blurred_tc,
|
||||||
|
mask_tex_pos: mask.mask_source_points,
|
||||||
|
threshold: mask.threshold,
|
||||||
|
};
|
||||||
|
let blurred_image_info = DescriptorImageInfo::default()
|
||||||
|
.image_view(cached.texture_view)
|
||||||
|
.image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
let mask_image_info = DescriptorImageInfo::default()
|
||||||
|
.image_view(mask.mask_view)
|
||||||
|
.image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
let writes = [
|
||||||
|
WriteDescriptorSet::default()
|
||||||
|
.dst_binding(0)
|
||||||
|
.descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||||
|
.image_info(slice::from_ref(&blurred_image_info)),
|
||||||
|
WriteDescriptorSet::default()
|
||||||
|
.dst_binding(1)
|
||||||
|
.descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||||
|
.image_info(slice::from_ref(&mask_image_info)),
|
||||||
|
];
|
||||||
|
unsafe {
|
||||||
|
dev.cmd_begin_rendering(buf, &rendering_info);
|
||||||
|
dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline);
|
||||||
|
dev.cmd_set_viewport(buf, 0, slice::from_ref(&viewport));
|
||||||
|
dev.cmd_set_scissor(buf, 0, slice::from_ref(&scissor));
|
||||||
|
self.device.push_descriptor.cmd_push_descriptor_set(
|
||||||
|
buf,
|
||||||
|
PipelineBindPoint::GRAPHICS,
|
||||||
|
pipeline.pipeline_layout,
|
||||||
|
0,
|
||||||
|
&writes,
|
||||||
|
);
|
||||||
|
dev.cmd_push_constants(
|
||||||
|
buf,
|
||||||
|
pipeline.pipeline_layout,
|
||||||
|
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
||||||
|
0,
|
||||||
|
uapi::as_bytes(&push),
|
||||||
|
);
|
||||||
|
dev.cmd_draw(buf, 4, 1, 0, 0);
|
||||||
|
dev.cmd_end_rendering(buf);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,31 @@ impl VulkanDevice {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn create_blur_descriptor_set_layout(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
sampler: &Rc<VulkanSampler>,
|
||||||
|
) -> Result<Rc<VulkanDescriptorSetLayout>, VulkanError> {
|
||||||
|
let immutable_sampler = [sampler.sampler];
|
||||||
|
let binding = DescriptorSetLayoutBinding::default()
|
||||||
|
.binding(0)
|
||||||
|
.stage_flags(ShaderStageFlags::FRAGMENT)
|
||||||
|
.descriptor_count(1)
|
||||||
|
.descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||||
|
.immutable_samplers(&immutable_sampler);
|
||||||
|
let create_info = DescriptorSetLayoutCreateInfo::default()
|
||||||
|
.bindings(slice::from_ref(&binding))
|
||||||
|
.flags(DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR_KHR);
|
||||||
|
let layout = unsafe { self.device.create_descriptor_set_layout(&create_info, None) };
|
||||||
|
let layout = layout.map_err(VulkanError::CreateDescriptorSetLayout)?;
|
||||||
|
Ok(Rc::new(VulkanDescriptorSetLayout {
|
||||||
|
device: self.clone(),
|
||||||
|
layout,
|
||||||
|
size: 0,
|
||||||
|
offsets: Default::default(),
|
||||||
|
_sampler: Some(sampler.clone()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn create_tex_sampler_descriptor_set_layout(
|
pub(super) fn create_tex_sampler_descriptor_set_layout(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
sampler: &Rc<VulkanSampler>,
|
sampler: &Rc<VulkanSampler>,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ use {
|
||||||
sampler::VulkanSampler,
|
sampler::VulkanSampler,
|
||||||
semaphore::VulkanSemaphore,
|
semaphore::VulkanSemaphore,
|
||||||
shaders::{
|
shaders::{
|
||||||
BLUR_COMPOSITE_FRAG, BLUR_COMPOSITE_VERT, BlurCompositePushConstants,
|
BLUR_COMPOSITE_FRAG, BLUR_COMPOSITE_VERT, BLUR_DOWN_FRAG, BLUR_UP_FRAG, BLUR_VERT,
|
||||||
|
BlurCompositePushConstants, BlurPushConstants,
|
||||||
ColorManagementData, EotfArgs, FILL_FRAG, FILL_VERT, FillPushConstants,
|
ColorManagementData, EotfArgs, FILL_FRAG, FILL_VERT, FillPushConstants,
|
||||||
InvEotfArgs, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, LEGACY_ROUNDED_FILL_FRAG,
|
InvEotfArgs, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, LEGACY_ROUNDED_FILL_FRAG,
|
||||||
LEGACY_ROUNDED_FILL_VERT, LEGACY_ROUNDED_TEX_FRAG, LEGACY_ROUNDED_TEX_VERT,
|
LEGACY_ROUNDED_FILL_VERT, LEGACY_ROUNDED_TEX_FRAG, LEGACY_ROUNDED_TEX_VERT,
|
||||||
|
|
@ -123,6 +124,12 @@ pub struct VulkanRenderer {
|
||||||
pub(super) blur_composite_frag_shader: Rc<VulkanShader>,
|
pub(super) blur_composite_frag_shader: Rc<VulkanShader>,
|
||||||
pub(super) blur_composite_descriptor_set_layout: Rc<VulkanDescriptorSetLayout>,
|
pub(super) blur_composite_descriptor_set_layout: Rc<VulkanDescriptorSetLayout>,
|
||||||
pub(super) blur_composite_pipelines: CopyHashMap<vk::Format, Rc<VulkanPipeline>>,
|
pub(super) blur_composite_pipelines: CopyHashMap<vk::Format, Rc<VulkanPipeline>>,
|
||||||
|
pub(super) blur_vert_shader: Rc<VulkanShader>,
|
||||||
|
pub(super) blur_down_frag_shader: Rc<VulkanShader>,
|
||||||
|
pub(super) blur_up_frag_shader: Rc<VulkanShader>,
|
||||||
|
pub(super) blur_descriptor_set_layout: Rc<VulkanDescriptorSetLayout>,
|
||||||
|
pub(super) blur_down_pipelines: CopyHashMap<vk::Format, Rc<VulkanPipeline>>,
|
||||||
|
pub(super) blur_up_pipelines: CopyHashMap<vk::Format, Rc<VulkanPipeline>>,
|
||||||
pub(super) defunct: Cell<bool>,
|
pub(super) defunct: Cell<bool>,
|
||||||
pub(super) pending_cpu_jobs: CopyHashMap<u64, PendingJob>,
|
pub(super) pending_cpu_jobs: CopyHashMap<u64, PendingJob>,
|
||||||
pub(super) shm_allocator: Rc<VulkanThreadedAllocator>,
|
pub(super) shm_allocator: Rc<VulkanThreadedAllocator>,
|
||||||
|
|
@ -229,13 +236,20 @@ enum VulkanOp {
|
||||||
struct VulkanBlurOp {
|
struct VulkanBlurOp {
|
||||||
rect: crate::gfx_api::FramebufferRect,
|
rect: crate::gfx_api::FramebufferRect,
|
||||||
passes: u8,
|
passes: u8,
|
||||||
|
offset: f32,
|
||||||
mask: Option<VulkanBlurMask>,
|
mask: Option<VulkanBlurMask>,
|
||||||
|
cache: Option<Rc<RefCell<Option<crate::gfx_api::BlurCacheEntry>>>>,
|
||||||
|
cache_epoch: u64,
|
||||||
|
cache_pixel_rect: [i32; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VulkanBlurMask {
|
struct VulkanBlurMask {
|
||||||
tex: Rc<VulkanImage>,
|
tex: Rc<VulkanImage>,
|
||||||
source: crate::gfx_api::SampleRect,
|
source: crate::gfx_api::SampleRect,
|
||||||
threshold: f32,
|
threshold: f32,
|
||||||
|
buffer_resv: Option<Rc<dyn BufferResv>>,
|
||||||
|
acquire_sync: Option<AcquireSync>,
|
||||||
|
release_sync: ReleaseSync,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VulkanTexOp {
|
struct VulkanTexOp {
|
||||||
|
|
@ -246,6 +260,7 @@ struct VulkanTexOp {
|
||||||
acquire_sync: Option<AcquireSync>,
|
acquire_sync: Option<AcquireSync>,
|
||||||
release_sync: ReleaseSync,
|
release_sync: ReleaseSync,
|
||||||
alpha: f32,
|
alpha: f32,
|
||||||
|
discard_alpha: f32,
|
||||||
source_type: TexSourceType,
|
source_type: TexSourceType,
|
||||||
copy_type: TexCopyType,
|
copy_type: TexCopyType,
|
||||||
alpha_mode: AlphaMode,
|
alpha_mode: AlphaMode,
|
||||||
|
|
@ -286,6 +301,7 @@ struct VulkanRoundedTexOp {
|
||||||
acquire_sync: Option<AcquireSync>,
|
acquire_sync: Option<AcquireSync>,
|
||||||
release_sync: ReleaseSync,
|
release_sync: ReleaseSync,
|
||||||
alpha: f32,
|
alpha: f32,
|
||||||
|
discard_alpha: f32,
|
||||||
source_type: TexSourceType,
|
source_type: TexSourceType,
|
||||||
copy_type: TexCopyType,
|
copy_type: TexCopyType,
|
||||||
alpha_mode: AlphaMode,
|
alpha_mode: AlphaMode,
|
||||||
|
|
@ -406,6 +422,10 @@ impl VulkanDevice {
|
||||||
let blur_composite_frag_shader = self.create_shader(BLUR_COMPOSITE_FRAG)?;
|
let blur_composite_frag_shader = self.create_shader(BLUR_COMPOSITE_FRAG)?;
|
||||||
let blur_composite_descriptor_set_layout =
|
let blur_composite_descriptor_set_layout =
|
||||||
self.create_blur_composite_descriptor_set_layout(&sampler)?;
|
self.create_blur_composite_descriptor_set_layout(&sampler)?;
|
||||||
|
let blur_vert_shader = self.create_shader(BLUR_VERT)?;
|
||||||
|
let blur_down_frag_shader = self.create_shader(BLUR_DOWN_FRAG)?;
|
||||||
|
let blur_up_frag_shader = self.create_shader(BLUR_UP_FRAG)?;
|
||||||
|
let blur_descriptor_set_layout = self.create_blur_descriptor_set_layout(&sampler)?;
|
||||||
let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?;
|
let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?;
|
||||||
let transfer_command_buffers = self
|
let transfer_command_buffers = self
|
||||||
.distinct_transfer_queue_family_idx
|
.distinct_transfer_queue_family_idx
|
||||||
|
|
@ -497,6 +517,12 @@ impl VulkanDevice {
|
||||||
blur_composite_frag_shader,
|
blur_composite_frag_shader,
|
||||||
blur_composite_descriptor_set_layout,
|
blur_composite_descriptor_set_layout,
|
||||||
blur_composite_pipelines: Default::default(),
|
blur_composite_pipelines: Default::default(),
|
||||||
|
blur_vert_shader,
|
||||||
|
blur_down_frag_shader,
|
||||||
|
blur_up_frag_shader,
|
||||||
|
blur_descriptor_set_layout,
|
||||||
|
blur_down_pipelines: Default::default(),
|
||||||
|
blur_up_pipelines: Default::default(),
|
||||||
defunct: Cell::new(false),
|
defunct: Cell::new(false),
|
||||||
pending_cpu_jobs: Default::default(),
|
pending_cpu_jobs: Default::default(),
|
||||||
shm_allocator,
|
shm_allocator,
|
||||||
|
|
@ -886,6 +912,128 @@ impl VulkanRenderer {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_or_create_blur_down_pipeline(
|
||||||
|
&self,
|
||||||
|
format: vk::Format,
|
||||||
|
) -> Result<Rc<VulkanPipeline>, VulkanError> {
|
||||||
|
if let Some(pl) = self.blur_down_pipelines.get(&format) {
|
||||||
|
return Ok(pl);
|
||||||
|
}
|
||||||
|
let pl = self.create_blur_pass_pipeline(format, &self.blur_down_frag_shader)?;
|
||||||
|
self.blur_down_pipelines.set(format, pl.clone());
|
||||||
|
Ok(pl)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn get_or_create_blur_up_pipeline(
|
||||||
|
&self,
|
||||||
|
format: vk::Format,
|
||||||
|
) -> Result<Rc<VulkanPipeline>, VulkanError> {
|
||||||
|
if let Some(pl) = self.blur_up_pipelines.get(&format) {
|
||||||
|
return Ok(pl);
|
||||||
|
}
|
||||||
|
let pl = self.create_blur_pass_pipeline(format, &self.blur_up_frag_shader)?;
|
||||||
|
self.blur_up_pipelines.set(format, pl.clone());
|
||||||
|
Ok(pl)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_blur_pass_pipeline(
|
||||||
|
&self,
|
||||||
|
format: vk::Format,
|
||||||
|
frag: &Rc<VulkanShader>,
|
||||||
|
) -> Result<Rc<VulkanPipeline>, VulkanError> {
|
||||||
|
use ash::vk::{
|
||||||
|
ColorComponentFlags, CullModeFlags, DynamicState, FrontFace,
|
||||||
|
GraphicsPipelineCreateInfo, PipelineCache, PipelineColorBlendAttachmentState,
|
||||||
|
PipelineColorBlendStateCreateInfo, PipelineDynamicStateCreateInfo,
|
||||||
|
PipelineInputAssemblyStateCreateInfo, PipelineLayoutCreateInfo,
|
||||||
|
PipelineMultisampleStateCreateInfo, PipelineRasterizationStateCreateInfo,
|
||||||
|
PipelineRenderingCreateInfo, PipelineShaderStageCreateInfo,
|
||||||
|
PipelineVertexInputStateCreateInfo, PipelineViewportStateCreateInfo, PolygonMode,
|
||||||
|
PrimitiveTopology, PushConstantRange, SampleCountFlags,
|
||||||
|
};
|
||||||
|
let dev = &self.device.device;
|
||||||
|
let push_range = PushConstantRange::default()
|
||||||
|
.stage_flags(ShaderStageFlags::FRAGMENT)
|
||||||
|
.offset(0)
|
||||||
|
.size(size_of::<BlurPushConstants>() as u32);
|
||||||
|
let set_layouts = [self.blur_descriptor_set_layout.layout];
|
||||||
|
let layout_info = PipelineLayoutCreateInfo::default()
|
||||||
|
.push_constant_ranges(slice::from_ref(&push_range))
|
||||||
|
.set_layouts(&set_layouts);
|
||||||
|
let pipeline_layout = unsafe { dev.create_pipeline_layout(&layout_info, None) };
|
||||||
|
let pipeline_layout = pipeline_layout.map_err(VulkanError::CreatePipelineLayout)?;
|
||||||
|
let destroy_layout =
|
||||||
|
run_on_drop::on_drop(|| unsafe { dev.destroy_pipeline_layout(pipeline_layout, None) });
|
||||||
|
let stages = [
|
||||||
|
PipelineShaderStageCreateInfo::default()
|
||||||
|
.stage(ShaderStageFlags::VERTEX)
|
||||||
|
.module(self.blur_vert_shader.module)
|
||||||
|
.name(c"main"),
|
||||||
|
PipelineShaderStageCreateInfo::default()
|
||||||
|
.stage(ShaderStageFlags::FRAGMENT)
|
||||||
|
.module(frag.module)
|
||||||
|
.name(c"main"),
|
||||||
|
];
|
||||||
|
let input_assembly_state = PipelineInputAssemblyStateCreateInfo::default()
|
||||||
|
.topology(PrimitiveTopology::TRIANGLE_STRIP);
|
||||||
|
let vertex_input_state = PipelineVertexInputStateCreateInfo::default();
|
||||||
|
let rasterization_state = PipelineRasterizationStateCreateInfo::default()
|
||||||
|
.polygon_mode(PolygonMode::FILL)
|
||||||
|
.cull_mode(CullModeFlags::NONE)
|
||||||
|
.line_width(1.0)
|
||||||
|
.front_face(FrontFace::COUNTER_CLOCKWISE);
|
||||||
|
let multisampling_state = PipelineMultisampleStateCreateInfo::default()
|
||||||
|
.sample_shading_enable(false)
|
||||||
|
.rasterization_samples(SampleCountFlags::TYPE_1);
|
||||||
|
let blending = PipelineColorBlendAttachmentState::default()
|
||||||
|
.color_write_mask(ColorComponentFlags::RGBA)
|
||||||
|
.blend_enable(false);
|
||||||
|
let color_blend_state =
|
||||||
|
PipelineColorBlendStateCreateInfo::default().attachments(slice::from_ref(&blending));
|
||||||
|
let dynamic_states = [DynamicState::VIEWPORT, DynamicState::SCISSOR];
|
||||||
|
let dynamic_state =
|
||||||
|
PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
|
||||||
|
let viewport_state = PipelineViewportStateCreateInfo::default()
|
||||||
|
.viewport_count(1)
|
||||||
|
.scissor_count(1);
|
||||||
|
let mut pipeline_rendering_create_info = PipelineRenderingCreateInfo::default()
|
||||||
|
.color_attachment_formats(slice::from_ref(&format));
|
||||||
|
let create_info = GraphicsPipelineCreateInfo::default()
|
||||||
|
.push_next(&mut pipeline_rendering_create_info)
|
||||||
|
.stages(&stages)
|
||||||
|
.input_assembly_state(&input_assembly_state)
|
||||||
|
.vertex_input_state(&vertex_input_state)
|
||||||
|
.rasterization_state(&rasterization_state)
|
||||||
|
.multisample_state(&multisampling_state)
|
||||||
|
.color_blend_state(&color_blend_state)
|
||||||
|
.dynamic_state(&dynamic_state)
|
||||||
|
.viewport_state(&viewport_state)
|
||||||
|
.layout(pipeline_layout);
|
||||||
|
let pipelines = unsafe {
|
||||||
|
dev.create_graphics_pipelines(
|
||||||
|
PipelineCache::null(),
|
||||||
|
slice::from_ref(&create_info),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let mut pipelines = pipelines
|
||||||
|
.map_err(|e| e.1)
|
||||||
|
.map_err(VulkanError::CreatePipeline)?;
|
||||||
|
let pipeline = pipelines.pop().unwrap();
|
||||||
|
destroy_layout.forget();
|
||||||
|
Ok(Rc::new(VulkanPipeline {
|
||||||
|
vert: self.blur_vert_shader.clone(),
|
||||||
|
_frag: frag.clone(),
|
||||||
|
pipeline_layout,
|
||||||
|
pipeline,
|
||||||
|
_descriptor_set_layouts: {
|
||||||
|
let mut v = ArrayVec::new();
|
||||||
|
v.push(self.blur_descriptor_set_layout.clone());
|
||||||
|
v
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn allocate_point(&self) -> u64 {
|
pub(super) fn allocate_point(&self) -> u64 {
|
||||||
self.last_point.fetch_add(1) + 1
|
self.last_point.fetch_add(1) + 1
|
||||||
}
|
}
|
||||||
|
|
@ -1263,6 +1411,7 @@ impl VulkanRenderer {
|
||||||
acquire_sync: Some(ct.acquire_sync.clone()),
|
acquire_sync: Some(ct.acquire_sync.clone()),
|
||||||
release_sync: ct.release_sync,
|
release_sync: ct.release_sync,
|
||||||
alpha: ct.alpha.unwrap_or_default(),
|
alpha: ct.alpha.unwrap_or_default(),
|
||||||
|
discard_alpha: ct.discard_alpha.unwrap_or(-1.0),
|
||||||
source_type,
|
source_type,
|
||||||
copy_type,
|
copy_type,
|
||||||
alpha_mode: ct.alpha_mode,
|
alpha_mode: ct.alpha_mode,
|
||||||
|
|
@ -1361,6 +1510,7 @@ impl VulkanRenderer {
|
||||||
acquire_sync: Some(ct.acquire_sync.clone()),
|
acquire_sync: Some(ct.acquire_sync.clone()),
|
||||||
release_sync: ct.release_sync,
|
release_sync: ct.release_sync,
|
||||||
alpha: ct.alpha.unwrap_or_default(),
|
alpha: ct.alpha.unwrap_or_default(),
|
||||||
|
discard_alpha: ct.discard_alpha.unwrap_or(-1.0),
|
||||||
source_type,
|
source_type,
|
||||||
copy_type,
|
copy_type,
|
||||||
alpha_mode: ct.alpha_mode,
|
alpha_mode: ct.alpha_mode,
|
||||||
|
|
@ -1376,9 +1526,6 @@ impl VulkanRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GfxApiOpt::BlurBackdrop(b) => {
|
GfxApiOpt::BlurBackdrop(b) => {
|
||||||
// Flush all pending ops in original order, then push a
|
|
||||||
// barrier op to FrameBuffer pass that will end + restart
|
|
||||||
// the render pass to do the blur work in between.
|
|
||||||
sync(memory);
|
sync(memory);
|
||||||
let mask = if let Some(m) = &b.mask {
|
let mask = if let Some(m) = &b.mask {
|
||||||
let tex = m.texture.clone().into_vk(&self.device.device)?;
|
let tex = m.texture.clone().into_vk(&self.device.device)?;
|
||||||
|
|
@ -1393,15 +1540,35 @@ impl VulkanRenderer {
|
||||||
tex,
|
tex,
|
||||||
source: m.source,
|
source: m.source,
|
||||||
threshold: m.threshold,
|
threshold: m.threshold,
|
||||||
|
buffer_resv: m.buffer_resv.clone(),
|
||||||
|
acquire_sync: Some(m.acquire_sync.clone()),
|
||||||
|
release_sync: m.release_sync,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
memory.ops[RenderPass::FrameBuffer].push(VulkanOp::BlurBarrier(VulkanBlurOp {
|
// Route to whichever pass actually contains the scene.
|
||||||
|
// The BlendBuffer holds the linearly-composed scene
|
||||||
|
// (background + workspace + translucent layers) and is
|
||||||
|
// copied into the FrameBuffer only at the end of the FB
|
||||||
|
// pass. Reading FB before that copy would sample an
|
||||||
|
// empty target and produce a black blur. If BB has been
|
||||||
|
// elided (no blended content this frame), fall back to
|
||||||
|
// FB which then carries the full scene itself.
|
||||||
|
let target_pass = if !memory.paint_regions[RenderPass::BlendBuffer].is_empty() {
|
||||||
|
RenderPass::BlendBuffer
|
||||||
|
} else {
|
||||||
|
RenderPass::FrameBuffer
|
||||||
|
};
|
||||||
|
memory.ops[target_pass].push(VulkanOp::BlurBarrier(VulkanBlurOp {
|
||||||
rect: b.rect,
|
rect: b.rect,
|
||||||
passes: b.passes,
|
passes: b.passes,
|
||||||
|
offset: b.offset,
|
||||||
mask,
|
mask,
|
||||||
|
cache: b.cache.clone(),
|
||||||
|
cache_epoch: b.cache_epoch,
|
||||||
|
cache_pixel_rect: b.cache_pixel_rect,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1586,7 +1753,7 @@ impl VulkanRenderer {
|
||||||
release_sync,
|
release_sync,
|
||||||
});
|
});
|
||||||
} else if let VulkanOp::BlurBarrier(b) = cmd
|
} else if let VulkanOp::BlurBarrier(b) = cmd
|
||||||
&& let Some(m) = &b.mask
|
&& let Some(m) = &mut b.mask
|
||||||
{
|
{
|
||||||
let tex = &m.tex;
|
let tex = &m.tex;
|
||||||
if tex.execution_version.replace(execution) != execution {
|
if tex.execution_version.replace(execution) != execution {
|
||||||
|
|
@ -1598,6 +1765,12 @@ impl VulkanRenderer {
|
||||||
if let VulkanImageMemory::DmaBuf(_) = &tex.ty {
|
if let VulkanImageMemory::DmaBuf(_) = &tex.ty {
|
||||||
memory.dmabuf_sample.push(tex.clone())
|
memory.dmabuf_sample.push(tex.clone())
|
||||||
}
|
}
|
||||||
|
memory.textures.push(UsedTexture {
|
||||||
|
tex: tex.clone(),
|
||||||
|
resv: m.buffer_resv.take(),
|
||||||
|
acquire_sync: m.acquire_sync.take().unwrap(),
|
||||||
|
release_sync: m.release_sync,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1929,6 +2102,7 @@ impl VulkanRenderer {
|
||||||
let push = TexPushConstants {
|
let push = TexPushConstants {
|
||||||
vertices: c.range_address,
|
vertices: c.range_address,
|
||||||
alpha: c.alpha,
|
alpha: c.alpha,
|
||||||
|
discard_threshold: c.discard_alpha,
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
db.cmd_set_descriptor_buffer_offsets(
|
db.cmd_set_descriptor_buffer_offsets(
|
||||||
|
|
@ -1966,6 +2140,7 @@ impl VulkanRenderer {
|
||||||
pos,
|
pos,
|
||||||
tex_pos,
|
tex_pos,
|
||||||
alpha: c.alpha,
|
alpha: c.alpha,
|
||||||
|
discard_threshold: c.discard_alpha,
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
dev.cmd_push_constants(
|
dev.cmd_push_constants(
|
||||||
|
|
@ -2045,6 +2220,7 @@ impl VulkanRenderer {
|
||||||
let push = RoundedTexPushConstants {
|
let push = RoundedTexPushConstants {
|
||||||
vertices: c.range_address,
|
vertices: c.range_address,
|
||||||
alpha: c.alpha,
|
alpha: c.alpha,
|
||||||
|
discard_threshold: c.discard_alpha,
|
||||||
size_x: c.size[0],
|
size_x: c.size[0],
|
||||||
size_y: c.size[1],
|
size_y: c.size[1],
|
||||||
corner_radius_tl: c.corner_radius[0],
|
corner_radius_tl: c.corner_radius[0],
|
||||||
|
|
@ -2088,6 +2264,7 @@ impl VulkanRenderer {
|
||||||
pos: c.target,
|
pos: c.target,
|
||||||
tex_pos: c.source,
|
tex_pos: c.source,
|
||||||
alpha: c.alpha,
|
alpha: c.alpha,
|
||||||
|
discard_threshold: c.discard_alpha,
|
||||||
size_x: c.size[0],
|
size_x: c.size[0],
|
||||||
size_y: c.size[1],
|
size_y: c.size[1],
|
||||||
corner_radius_tl: c.corner_radius[0],
|
corner_radius_tl: c.corner_radius[0],
|
||||||
|
|
@ -2109,10 +2286,8 @@ impl VulkanRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VulkanOp::BlurBarrier(blur) => {
|
VulkanOp::BlurBarrier(blur) => {
|
||||||
// Blur is only meaningful in the FrameBuffer pass.
|
// BlurBarrier is pushed to exactly one pass in convert_ops
|
||||||
if pass != RenderPass::FrameBuffer {
|
// (BB if present, else FB), so no per-pass gating is needed.
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// End the current dynamic render pass, run the blur work
|
// End the current dynamic render pass, run the blur work
|
||||||
// (image-blit cascade between scratch images), and resume
|
// (image-blit cascade between scratch images), and resume
|
||||||
// the render pass with LOAD so subsequent draws layer on
|
// the render pass with LOAD so subsequent draws layer on
|
||||||
|
|
@ -2132,14 +2307,55 @@ impl VulkanRenderer {
|
||||||
threshold: m.threshold,
|
threshold: m.threshold,
|
||||||
_phantom: std::marker::PhantomData,
|
_phantom: std::marker::PhantomData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cache lookup: a hit lets us skip the entire blur cascade.
|
||||||
|
// Only masked blurs are cached. The masked path leaves the
|
||||||
|
// blurred scratch image in SHADER_READ_ONLY_OPTIMAL, which
|
||||||
|
// is the layout required by the cache-hit composite path.
|
||||||
|
let cached_blur: Option<Rc<VulkanImage>> = mask_record
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|_| blur.cache.as_ref())
|
||||||
|
.and_then(|slot| {
|
||||||
|
let slot_borrow = slot.borrow();
|
||||||
|
slot_borrow.as_ref().and_then(|entry| {
|
||||||
|
if entry.epoch == blur.cache_epoch
|
||||||
|
&& entry.passes == blur.passes
|
||||||
|
&& entry.offset == blur.offset
|
||||||
|
&& entry.pixel_rect == blur.cache_pixel_rect
|
||||||
|
{
|
||||||
|
entry.image.clone().into_vk(&self.device.device).ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut produced_blur: Option<Rc<VulkanImage>> = None;
|
||||||
self.record_blur(
|
self.record_blur(
|
||||||
buf,
|
buf,
|
||||||
target,
|
target,
|
||||||
rect_arr,
|
rect_arr,
|
||||||
blur.passes,
|
blur.passes,
|
||||||
|
blur.offset,
|
||||||
&mut local_blur_scratch,
|
&mut local_blur_scratch,
|
||||||
mask_record.as_ref(),
|
mask_record.as_ref(),
|
||||||
|
cached_blur.as_ref(),
|
||||||
|
&mut produced_blur,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// On a masked cache miss, store the freshly-blurred image
|
||||||
|
// for the next frame to reuse.
|
||||||
|
if let (Some(_), Some(slot), Some(image)) =
|
||||||
|
(mask_record.as_ref(), blur.cache.as_ref(), produced_blur)
|
||||||
|
{
|
||||||
|
*slot.borrow_mut() = Some(crate::gfx_api::BlurCacheEntry {
|
||||||
|
pixel_rect: blur.cache_pixel_rect,
|
||||||
|
passes: blur.passes,
|
||||||
|
offset: blur.offset,
|
||||||
|
epoch: blur.cache_epoch,
|
||||||
|
image,
|
||||||
|
});
|
||||||
|
}
|
||||||
self.begin_rendering_load(buf, target);
|
self.begin_rendering_load(buf, target);
|
||||||
// Pipeline state is invalidated across the render-pass
|
// Pipeline state is invalidated across the render-pass
|
||||||
// break — force re-bind on next draw.
|
// break — force re-bind on next draw.
|
||||||
|
|
@ -2679,6 +2895,7 @@ impl VulkanRenderer {
|
||||||
let width = fb.width as f32;
|
let width = fb.width as f32;
|
||||||
let height = fb.height as f32;
|
let height = fb.height as f32;
|
||||||
let mut tag = 0;
|
let mut tag = 0;
|
||||||
|
let mut blur_rects: Vec<Rect> = Vec::new();
|
||||||
for opt in opts.iter().rev() {
|
for opt in opts.iter().rev() {
|
||||||
let (opaque, fb_rect) = match opt {
|
let (opaque, fb_rect) = match opt {
|
||||||
GfxApiOpt::Sync => continue,
|
GfxApiOpt::Sync => continue,
|
||||||
|
|
@ -2706,7 +2923,10 @@ impl VulkanRenderer {
|
||||||
(false, rf.rect)
|
(false, rf.rect)
|
||||||
}
|
}
|
||||||
GfxApiOpt::RoundedCopyTexture(ct) => (false, ct.target),
|
GfxApiOpt::RoundedCopyTexture(ct) => (false, ct.target),
|
||||||
GfxApiOpt::BlurBackdrop(_) => continue,
|
GfxApiOpt::BlurBackdrop(b) => {
|
||||||
|
blur_rects.push(b.rect.to_rect(width, height));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if opaque || bb.is_none() {
|
if opaque || bb.is_none() {
|
||||||
tag |= 1;
|
tag |= 1;
|
||||||
|
|
@ -2719,6 +2939,20 @@ impl VulkanRenderer {
|
||||||
}
|
}
|
||||||
memory.regions_2.push(rect.with_tag(tag));
|
memory.regions_2.push(rect.with_tag(tag));
|
||||||
}
|
}
|
||||||
|
// Force blur source rects into the effective damage region. The blur
|
||||||
|
// cascade reads its source from BB (or FB), and both buffers persist
|
||||||
|
// their contents across frames in undamaged regions. Without this,
|
||||||
|
// a cache-miss cascade can sample a stale composite — including the
|
||||||
|
// blur surface's own previously-drawn body — and re-blur it,
|
||||||
|
// producing visible double-shadow artifacts at the blur boundary.
|
||||||
|
let blur_region_owned = Region::from_rects2(&blur_rects);
|
||||||
|
let expanded_region;
|
||||||
|
let region: &Region = if blur_region_owned.is_empty() {
|
||||||
|
region
|
||||||
|
} else {
|
||||||
|
expanded_region = region.union_cow(&blur_region_owned).into_owned();
|
||||||
|
&expanded_region
|
||||||
|
};
|
||||||
let clear_region = if clear.is_some() {
|
let clear_region = if clear.is_some() {
|
||||||
let opaque_region = Region::from_rects2(&memory.regions_1);
|
let opaque_region = Region::from_rects2(&memory.regions_1);
|
||||||
region.subtract_cow(&opaque_region)
|
region.subtract_cow(&opaque_region)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,9 @@ pub const ROUNDED_TEX_VERT: &[u8] = include_bytes!("shaders_bin/rounded_tex.vert
|
||||||
pub const ROUNDED_TEX_FRAG: &[u8] = include_bytes!("shaders_bin/rounded_tex.frag.spv");
|
pub const ROUNDED_TEX_FRAG: &[u8] = include_bytes!("shaders_bin/rounded_tex.frag.spv");
|
||||||
pub const BLUR_COMPOSITE_VERT: &[u8] = include_bytes!("shaders_bin/blur_composite.vert.spv");
|
pub const BLUR_COMPOSITE_VERT: &[u8] = include_bytes!("shaders_bin/blur_composite.vert.spv");
|
||||||
pub const BLUR_COMPOSITE_FRAG: &[u8] = include_bytes!("shaders_bin/blur_composite.frag.spv");
|
pub const BLUR_COMPOSITE_FRAG: &[u8] = include_bytes!("shaders_bin/blur_composite.frag.spv");
|
||||||
|
pub const BLUR_VERT: &[u8] = include_bytes!("shaders_bin/blur.vert.spv");
|
||||||
|
pub const BLUR_DOWN_FRAG: &[u8] = include_bytes!("shaders_bin/blur_down.frag.spv");
|
||||||
|
pub const BLUR_UP_FRAG: &[u8] = include_bytes!("shaders_bin/blur_up.frag.spv");
|
||||||
pub const LEGACY_ROUNDED_FILL_VERT: &[u8] =
|
pub const LEGACY_ROUNDED_FILL_VERT: &[u8] =
|
||||||
include_bytes!("shaders_bin/legacy_rounded_fill.vert.spv");
|
include_bytes!("shaders_bin/legacy_rounded_fill.vert.spv");
|
||||||
pub const LEGACY_ROUNDED_FILL_FRAG: &[u8] =
|
pub const LEGACY_ROUNDED_FILL_FRAG: &[u8] =
|
||||||
|
|
@ -69,6 +72,7 @@ unsafe impl Packed for TexVertex {}
|
||||||
pub struct TexPushConstants {
|
pub struct TexPushConstants {
|
||||||
pub vertices: DeviceAddress,
|
pub vertices: DeviceAddress,
|
||||||
pub alpha: f32,
|
pub alpha: f32,
|
||||||
|
pub discard_threshold: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Packed for TexPushConstants {}
|
unsafe impl Packed for TexPushConstants {}
|
||||||
|
|
@ -109,6 +113,7 @@ pub struct LegacyTexPushConstants {
|
||||||
pub pos: [[f32; 2]; 4],
|
pub pos: [[f32; 2]; 4],
|
||||||
pub tex_pos: [[f32; 2]; 4],
|
pub tex_pos: [[f32; 2]; 4],
|
||||||
pub alpha: f32,
|
pub alpha: f32,
|
||||||
|
pub discard_threshold: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Packed for LegacyTexPushConstants {}
|
unsafe impl Packed for LegacyTexPushConstants {}
|
||||||
|
|
@ -148,6 +153,7 @@ unsafe impl Packed for LegacyRoundedFillPushConstants {}
|
||||||
pub struct RoundedTexPushConstants {
|
pub struct RoundedTexPushConstants {
|
||||||
pub vertices: DeviceAddress,
|
pub vertices: DeviceAddress,
|
||||||
pub alpha: f32,
|
pub alpha: f32,
|
||||||
|
pub discard_threshold: f32,
|
||||||
pub size_x: f32,
|
pub size_x: f32,
|
||||||
pub size_y: f32,
|
pub size_y: f32,
|
||||||
pub corner_radius_tl: f32,
|
pub corner_radius_tl: f32,
|
||||||
|
|
@ -165,6 +171,7 @@ pub struct LegacyRoundedTexPushConstants {
|
||||||
pub pos: [[f32; 2]; 4],
|
pub pos: [[f32; 2]; 4],
|
||||||
pub tex_pos: [[f32; 2]; 4],
|
pub tex_pos: [[f32; 2]; 4],
|
||||||
pub alpha: f32,
|
pub alpha: f32,
|
||||||
|
pub discard_threshold: f32,
|
||||||
pub size_x: f32,
|
pub size_x: f32,
|
||||||
pub size_y: f32,
|
pub size_y: f32,
|
||||||
pub corner_radius_tl: f32,
|
pub corner_radius_tl: f32,
|
||||||
|
|
@ -195,6 +202,15 @@ pub struct BlurCompositePushConstants {
|
||||||
|
|
||||||
unsafe impl Packed for BlurCompositePushConstants {}
|
unsafe impl Packed for BlurCompositePushConstants {}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct BlurPushConstants {
|
||||||
|
pub halfpixel: [f32; 2],
|
||||||
|
pub offset: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Packed for BlurPushConstants {}
|
||||||
|
|
||||||
impl VulkanDevice {
|
impl VulkanDevice {
|
||||||
pub(super) fn create_shader(
|
pub(super) fn create_shader(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
|
|
|
||||||
26
src/gfx_apis/vulkan/shaders/blur.vert
Normal file
26
src/gfx_apis/vulkan/shaders/blur.vert
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) out vec2 v_texcoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 pos;
|
||||||
|
switch (gl_VertexIndex) {
|
||||||
|
case 0:
|
||||||
|
pos = vec2( 1.0, -1.0);
|
||||||
|
v_texcoord = vec2(1.0, 0.0);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
pos = vec2(-1.0, -1.0);
|
||||||
|
v_texcoord = vec2(0.0, 0.0);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
pos = vec2( 1.0, 1.0);
|
||||||
|
v_texcoord = vec2(1.0, 1.0);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
pos = vec2(-1.0, 1.0);
|
||||||
|
v_texcoord = vec2(0.0, 1.0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
gl_Position = vec4(pos, 0.0, 1.0);
|
||||||
|
}
|
||||||
21
src/gfx_apis/vulkan/shaders/blur_down.frag
Normal file
21
src/gfx_apis/vulkan/shaders/blur_down.frag
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform sampler2D tex;
|
||||||
|
|
||||||
|
layout(push_constant, std430) uniform Data {
|
||||||
|
vec2 halfpixel;
|
||||||
|
float offset;
|
||||||
|
} data;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_texcoord;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 hp = data.halfpixel * data.offset;
|
||||||
|
vec4 sum = textureLod(tex, v_texcoord, 0.0) * 4.0;
|
||||||
|
sum += textureLod(tex, v_texcoord - hp, 0.0);
|
||||||
|
sum += textureLod(tex, v_texcoord + hp, 0.0);
|
||||||
|
sum += textureLod(tex, v_texcoord + vec2(hp.x, -hp.y), 0.0);
|
||||||
|
sum += textureLod(tex, v_texcoord - vec2(hp.x, -hp.y), 0.0);
|
||||||
|
out_color = sum / 8.0;
|
||||||
|
}
|
||||||
24
src/gfx_apis/vulkan/shaders/blur_up.frag
Normal file
24
src/gfx_apis/vulkan/shaders/blur_up.frag
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform sampler2D tex;
|
||||||
|
|
||||||
|
layout(push_constant, std430) uniform Data {
|
||||||
|
vec2 halfpixel;
|
||||||
|
float offset;
|
||||||
|
} data;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_texcoord;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 hp = data.halfpixel * data.offset;
|
||||||
|
vec4 sum = textureLod(tex, v_texcoord + vec2(-hp.x * 2.0, 0.0), 0.0);
|
||||||
|
sum += textureLod(tex, v_texcoord + vec2(-hp.x, hp.y), 0.0) * 2.0;
|
||||||
|
sum += textureLod(tex, v_texcoord + vec2(0.0, hp.y * 2.0), 0.0);
|
||||||
|
sum += textureLod(tex, v_texcoord + vec2(hp.x, hp.y), 0.0) * 2.0;
|
||||||
|
sum += textureLod(tex, v_texcoord + vec2(hp.x * 2.0, 0.0), 0.0);
|
||||||
|
sum += textureLod(tex, v_texcoord + vec2(hp.x, -hp.y), 0.0) * 2.0;
|
||||||
|
sum += textureLod(tex, v_texcoord + vec2(0.0, -hp.y * 2.0), 0.0);
|
||||||
|
sum += textureLod(tex, v_texcoord + vec2(-hp.x, -hp.y), 0.0) * 2.0;
|
||||||
|
out_color = sum / 12.0;
|
||||||
|
}
|
||||||
BIN
src/gfx_apis/vulkan/shaders_bin/blur.vert.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/blur.vert.spv
Normal file
Binary file not shown.
BIN
src/gfx_apis/vulkan/shaders_bin/blur_down.frag.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/blur_down.frag.spv
Normal file
Binary file not shown.
BIN
src/gfx_apis/vulkan/shaders_bin/blur_up.frag.spv
Normal file
BIN
src/gfx_apis/vulkan/shaders_bin/blur_up.frag.spv
Normal file
Binary file not shown.
|
|
@ -1,6 +1,9 @@
|
||||||
302a9f250bdc4f8e0e71a9f77c9a8a7aa55fd003bc91c2422a700c4abd83f54e src/gfx_apis/vulkan/shaders/alpha_modes.glsl
|
302a9f250bdc4f8e0e71a9f77c9a8a7aa55fd003bc91c2422a700c4abd83f54e src/gfx_apis/vulkan/shaders/alpha_modes.glsl
|
||||||
|
65acbe7a6496279fa22f520ad2036d3e14a7cb1707c6a509ce7858adc4a2dcba src/gfx_apis/vulkan/shaders/blur.vert
|
||||||
16ad6f1eb029ccce5e0204a7d79709b05a8a708133feaf8bb20a24371de25ed7 src/gfx_apis/vulkan/shaders/blur_composite.frag
|
16ad6f1eb029ccce5e0204a7d79709b05a8a708133feaf8bb20a24371de25ed7 src/gfx_apis/vulkan/shaders/blur_composite.frag
|
||||||
6399e23afa2e07c98b9fd1a4e853ea974a9958547ce65734846483bd7cbc8461 src/gfx_apis/vulkan/shaders/blur_composite.vert
|
6399e23afa2e07c98b9fd1a4e853ea974a9958547ce65734846483bd7cbc8461 src/gfx_apis/vulkan/shaders/blur_composite.vert
|
||||||
|
a04b2453c39efb018754fc25d45a369b5813359c55fad1c99020804cbb3a18e0 src/gfx_apis/vulkan/shaders/blur_down.frag
|
||||||
|
f6d51f3b5410387d1474529c44e71bfdc31ceb80174ea6e3e4c2df30d03f11c3 src/gfx_apis/vulkan/shaders/blur_up.frag
|
||||||
b6a0df1e231fab533499329636b7a580384784418baee06c147af5fcc384cf5c src/gfx_apis/vulkan/shaders/eotfs.glsl
|
b6a0df1e231fab533499329636b7a580384784418baee06c147af5fcc384cf5c src/gfx_apis/vulkan/shaders/eotfs.glsl
|
||||||
8a38df18851cd13884499820f26939fb7319f45d913d867f254d8118d59fb117 src/gfx_apis/vulkan/shaders/fill.common.glsl
|
8a38df18851cd13884499820f26939fb7319f45d913d867f254d8118d59fb117 src/gfx_apis/vulkan/shaders/fill.common.glsl
|
||||||
21c488d12aa5ad2f109ec44cb856dfe837e02ea9025b5ed64439d742c17cbf30 src/gfx_apis/vulkan/shaders/fill.frag
|
21c488d12aa5ad2f109ec44cb856dfe837e02ea9025b5ed64439d742c17cbf30 src/gfx_apis/vulkan/shaders/fill.frag
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue