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",
|
||||
"blur_composite.vert",
|
||||
"blur_composite.frag",
|
||||
"blur.vert",
|
||||
"blur_down.frag",
|
||||
"blur_up.frag",
|
||||
"legacy/fill.frag",
|
||||
"legacy/fill.vert",
|
||||
"legacy/tex.vert",
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ use {
|
|||
crate::gfx_apis::vulkan::{
|
||||
VulkanError,
|
||||
image::{QueueFamily, QueueState, VulkanImage, VulkanImageMemory},
|
||||
pipeline::VulkanPipeline,
|
||||
renderer::VulkanRenderer,
|
||||
shaders::BlurCompositePushConstants,
|
||||
shaders::{BlurCompositePushConstants, BlurPushConstants},
|
||||
},
|
||||
ash::vk::{
|
||||
AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BlitImageInfo2, CommandBuffer,
|
||||
|
|
@ -16,13 +17,14 @@ use {
|
|||
},
|
||||
gpu_alloc::UsageFlags,
|
||||
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(
|
||||
ImageUsageFlags::TRANSFER_SRC.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> {
|
||||
|
|
@ -42,12 +44,17 @@ impl VulkanRenderer {
|
|||
) -> Result<Rc<VulkanImage>, VulkanError> {
|
||||
let key = (width, height, format);
|
||||
let cached = &mut *self.blur_scratch.borrow_mut();
|
||||
let entry = cached.entry(key);
|
||||
if let Entry::Occupied(e) = &entry
|
||||
&& let Some(img) = e.get().upgrade()
|
||||
{
|
||||
return Ok(img);
|
||||
|
||||
if let Some(weak) = cached.get(&key) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let create_info = ImageCreateInfo::default()
|
||||
.image_type(ImageType::TYPE_2D)
|
||||
.format(format)
|
||||
|
|
@ -77,7 +84,6 @@ impl VulkanRenderer {
|
|||
.bind_image_memory(image, allocation.memory, allocation.offset)
|
||||
};
|
||||
res.map_err(VulkanError::BindImageMemory)?;
|
||||
// No view needed (we only blit), but VulkanImage requires one.
|
||||
let image_view_create_info = ImageViewCreateInfo::default()
|
||||
.image(image)
|
||||
.format(format)
|
||||
|
|
@ -96,8 +102,6 @@ impl VulkanRenderer {
|
|||
};
|
||||
let view = view.map_err(VulkanError::CreateImageView)?;
|
||||
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 {
|
||||
renderer: self.clone(),
|
||||
format: crate::gfx_apis::vulkan::format::BLEND_FORMAT,
|
||||
|
|
@ -117,28 +121,30 @@ impl VulkanRenderer {
|
|||
sampled_image_descriptor: None,
|
||||
execution_version: Default::default(),
|
||||
});
|
||||
match entry {
|
||||
Entry::Occupied(mut e) => {
|
||||
e.insert(Rc::downgrade(&img));
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(Rc::downgrade(&img));
|
||||
}
|
||||
}
|
||||
cached.insert(key, Rc::downgrade(&img));
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
/// Records a backdrop blur of the given pixel rect on the target image.
|
||||
/// Caller is responsible for ending the current dynamic render pass before
|
||||
/// invoking, and for restarting it afterward (with LOAD).
|
||||
/// Records a dual-Kawase backdrop blur of the given pixel rect on the target
|
||||
/// image. Caller is responsible for ending the current dynamic render pass
|
||||
/// 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(
|
||||
self: &Rc<Self>,
|
||||
buf: CommandBuffer,
|
||||
target: &VulkanImage,
|
||||
rect: [i32; 4],
|
||||
passes: u8,
|
||||
offset: f32,
|
||||
scratch_out: &mut Vec<Rc<VulkanImage>>,
|
||||
mask: Option<&BlurMaskRecord<'_>>,
|
||||
cached_blur: Option<&Rc<VulkanImage>>,
|
||||
out_blur_image: &mut Option<Rc<VulkanImage>>,
|
||||
) -> Result<(), VulkanError> {
|
||||
let [x1, y1, x2, y2] = rect;
|
||||
let x1 = x1.max(0).min(target.width as i32);
|
||||
|
|
@ -150,7 +156,26 @@ impl VulkanRenderer {
|
|||
if w < 4 || h < 4 {
|
||||
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 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)?);
|
||||
}
|
||||
|
||||
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()
|
||||
.aspect_mask(ImageAspectFlags::COLOR)
|
||||
.layer_count(1)
|
||||
|
|
@ -203,7 +229,7 @@ impl VulkanRenderer {
|
|||
};
|
||||
|
||||
// Step 1: target COLOR_ATTACHMENT -> TRANSFER_SRC.
|
||||
// Step 1: levels[0] UNDEFINED -> TRANSFER_DST.
|
||||
// levels[0] -> TRANSFER_DST (discard prior contents).
|
||||
do_barriers(&[
|
||||
barrier(
|
||||
target.image,
|
||||
|
|
@ -224,6 +250,7 @@ impl VulkanRenderer {
|
|||
AccessFlags2::TRANSFER_WRITE,
|
||||
),
|
||||
]);
|
||||
levels[0].is_undefined.set(false);
|
||||
|
||||
// Step 2: blit target rect -> levels[0] full.
|
||||
let blit = ImageBlit2::default()
|
||||
|
|
@ -252,139 +279,153 @@ impl VulkanRenderer {
|
|||
dev.cmd_blit_image2(buf, &blit_info);
|
||||
}
|
||||
|
||||
// Down passes: levels[i-1] -> levels[i] with linear filter.
|
||||
for i in 1..=passes as usize {
|
||||
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::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));
|
||||
// Step 3: levels[0] TRANSFER_DST -> SHADER_READ_ONLY for sampling in
|
||||
// the down pass.
|
||||
do_barriers(&[barrier(
|
||||
levels[0].image,
|
||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
PipelineStageFlags2::TRANSFER,
|
||||
AccessFlags2::TRANSFER_WRITE,
|
||||
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
AccessFlags2::SHADER_SAMPLED_READ,
|
||||
)]);
|
||||
|
||||
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_blit_image2(buf, &blit_info);
|
||||
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] -> levels[i] with linear filter.
|
||||
// Up passes: levels[i+1] (SHADER_READ_ONLY) -> levels[i] (COLOR_ATT).
|
||||
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);
|
||||
}
|
||||
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:
|
||||
// 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,
|
||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
PipelineStageFlags2::TRANSFER,
|
||||
AccessFlags2::TRANSFER_WRITE,
|
||||
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
AccessFlags2::SHADER_SAMPLED_READ,
|
||||
),
|
||||
barrier(
|
||||
target.image,
|
||||
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
PipelineStageFlags2::TRANSFER,
|
||||
AccessFlags2::TRANSFER_READ,
|
||||
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||
AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ,
|
||||
),
|
||||
]);
|
||||
// Masked composite path: restore target to COLOR_ATTACHMENT and
|
||||
// draw the composite shader sampling levels[0] + mask.
|
||||
do_barriers(&[barrier(
|
||||
target.image,
|
||||
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
PipelineStageFlags2::TRANSFER,
|
||||
AccessFlags2::TRANSFER_READ,
|
||||
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||
AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ,
|
||||
)]);
|
||||
|
||||
let pipeline = self.get_or_create_blur_composite_pipeline(target.format.vk_format)?;
|
||||
|
||||
|
|
@ -470,14 +511,15 @@ impl VulkanRenderer {
|
|||
dev.cmd_end_rendering(buf);
|
||||
}
|
||||
} 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(&[
|
||||
barrier(
|
||||
levels[0].image,
|
||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
ImageLayout::TRANSFER_SRC_OPTIMAL,
|
||||
PipelineStageFlags2::TRANSFER,
|
||||
AccessFlags2::TRANSFER_WRITE,
|
||||
PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
AccessFlags2::SHADER_SAMPLED_READ,
|
||||
PipelineStageFlags2::TRANSFER,
|
||||
AccessFlags2::TRANSFER_READ,
|
||||
),
|
||||
|
|
@ -516,21 +558,158 @@ impl VulkanRenderer {
|
|||
unsafe {
|
||||
dev.cmd_blit_image2(buf, &blit_info);
|
||||
}
|
||||
|
||||
// Restore target to COLOR_ATTACHMENT_OPTIMAL.
|
||||
do_barriers(&[barrier(
|
||||
target.image,
|
||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
PipelineStageFlags2::TRANSFER,
|
||||
AccessFlags2::TRANSFER_WRITE,
|
||||
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||
AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ,
|
||||
)]);
|
||||
// Restore target to COLOR_ATTACHMENT for the resumed render pass.
|
||||
// Also push levels[0] back to SHADER_READ_ONLY so its tracked layout
|
||||
// matches what the cache-hit fast path expects on next frame.
|
||||
do_barriers(&[
|
||||
barrier(
|
||||
target.image,
|
||||
ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
PipelineStageFlags2::TRANSFER,
|
||||
AccessFlags2::TRANSFER_WRITE,
|
||||
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
||||
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.
|
||||
scratch_out.extend(levels);
|
||||
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(
|
||||
self: &Rc<Self>,
|
||||
sampler: &Rc<VulkanSampler>,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ use {
|
|||
sampler::VulkanSampler,
|
||||
semaphore::VulkanSemaphore,
|
||||
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,
|
||||
InvEotfArgs, LEGACY_FILL_FRAG, LEGACY_FILL_VERT, LEGACY_ROUNDED_FILL_FRAG,
|
||||
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_descriptor_set_layout: Rc<VulkanDescriptorSetLayout>,
|
||||
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) pending_cpu_jobs: CopyHashMap<u64, PendingJob>,
|
||||
pub(super) shm_allocator: Rc<VulkanThreadedAllocator>,
|
||||
|
|
@ -229,13 +236,20 @@ enum VulkanOp {
|
|||
struct VulkanBlurOp {
|
||||
rect: crate::gfx_api::FramebufferRect,
|
||||
passes: u8,
|
||||
offset: f32,
|
||||
mask: Option<VulkanBlurMask>,
|
||||
cache: Option<Rc<RefCell<Option<crate::gfx_api::BlurCacheEntry>>>>,
|
||||
cache_epoch: u64,
|
||||
cache_pixel_rect: [i32; 4],
|
||||
}
|
||||
|
||||
struct VulkanBlurMask {
|
||||
tex: Rc<VulkanImage>,
|
||||
source: crate::gfx_api::SampleRect,
|
||||
threshold: f32,
|
||||
buffer_resv: Option<Rc<dyn BufferResv>>,
|
||||
acquire_sync: Option<AcquireSync>,
|
||||
release_sync: ReleaseSync,
|
||||
}
|
||||
|
||||
struct VulkanTexOp {
|
||||
|
|
@ -246,6 +260,7 @@ struct VulkanTexOp {
|
|||
acquire_sync: Option<AcquireSync>,
|
||||
release_sync: ReleaseSync,
|
||||
alpha: f32,
|
||||
discard_alpha: f32,
|
||||
source_type: TexSourceType,
|
||||
copy_type: TexCopyType,
|
||||
alpha_mode: AlphaMode,
|
||||
|
|
@ -286,6 +301,7 @@ struct VulkanRoundedTexOp {
|
|||
acquire_sync: Option<AcquireSync>,
|
||||
release_sync: ReleaseSync,
|
||||
alpha: f32,
|
||||
discard_alpha: f32,
|
||||
source_type: TexSourceType,
|
||||
copy_type: TexCopyType,
|
||||
alpha_mode: AlphaMode,
|
||||
|
|
@ -406,6 +422,10 @@ impl VulkanDevice {
|
|||
let blur_composite_frag_shader = self.create_shader(BLUR_COMPOSITE_FRAG)?;
|
||||
let blur_composite_descriptor_set_layout =
|
||||
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 transfer_command_buffers = self
|
||||
.distinct_transfer_queue_family_idx
|
||||
|
|
@ -497,6 +517,12 @@ impl VulkanDevice {
|
|||
blur_composite_frag_shader,
|
||||
blur_composite_descriptor_set_layout,
|
||||
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),
|
||||
pending_cpu_jobs: Default::default(),
|
||||
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 {
|
||||
self.last_point.fetch_add(1) + 1
|
||||
}
|
||||
|
|
@ -1263,6 +1411,7 @@ impl VulkanRenderer {
|
|||
acquire_sync: Some(ct.acquire_sync.clone()),
|
||||
release_sync: ct.release_sync,
|
||||
alpha: ct.alpha.unwrap_or_default(),
|
||||
discard_alpha: ct.discard_alpha.unwrap_or(-1.0),
|
||||
source_type,
|
||||
copy_type,
|
||||
alpha_mode: ct.alpha_mode,
|
||||
|
|
@ -1361,6 +1510,7 @@ impl VulkanRenderer {
|
|||
acquire_sync: Some(ct.acquire_sync.clone()),
|
||||
release_sync: ct.release_sync,
|
||||
alpha: ct.alpha.unwrap_or_default(),
|
||||
discard_alpha: ct.discard_alpha.unwrap_or(-1.0),
|
||||
source_type,
|
||||
copy_type,
|
||||
alpha_mode: ct.alpha_mode,
|
||||
|
|
@ -1376,9 +1526,6 @@ impl VulkanRenderer {
|
|||
}
|
||||
}
|
||||
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);
|
||||
let mask = if let Some(m) = &b.mask {
|
||||
let tex = m.texture.clone().into_vk(&self.device.device)?;
|
||||
|
|
@ -1393,15 +1540,35 @@ impl VulkanRenderer {
|
|||
tex,
|
||||
source: m.source,
|
||||
threshold: m.threshold,
|
||||
buffer_resv: m.buffer_resv.clone(),
|
||||
acquire_sync: Some(m.acquire_sync.clone()),
|
||||
release_sync: m.release_sync,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
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,
|
||||
passes: b.passes,
|
||||
offset: b.offset,
|
||||
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,
|
||||
});
|
||||
} else if let VulkanOp::BlurBarrier(b) = cmd
|
||||
&& let Some(m) = &b.mask
|
||||
&& let Some(m) = &mut b.mask
|
||||
{
|
||||
let tex = &m.tex;
|
||||
if tex.execution_version.replace(execution) != execution {
|
||||
|
|
@ -1598,6 +1765,12 @@ impl VulkanRenderer {
|
|||
if let VulkanImageMemory::DmaBuf(_) = &tex.ty {
|
||||
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 {
|
||||
vertices: c.range_address,
|
||||
alpha: c.alpha,
|
||||
discard_threshold: c.discard_alpha,
|
||||
};
|
||||
unsafe {
|
||||
db.cmd_set_descriptor_buffer_offsets(
|
||||
|
|
@ -1966,6 +2140,7 @@ impl VulkanRenderer {
|
|||
pos,
|
||||
tex_pos,
|
||||
alpha: c.alpha,
|
||||
discard_threshold: c.discard_alpha,
|
||||
};
|
||||
unsafe {
|
||||
dev.cmd_push_constants(
|
||||
|
|
@ -2045,6 +2220,7 @@ impl VulkanRenderer {
|
|||
let push = RoundedTexPushConstants {
|
||||
vertices: c.range_address,
|
||||
alpha: c.alpha,
|
||||
discard_threshold: c.discard_alpha,
|
||||
size_x: c.size[0],
|
||||
size_y: c.size[1],
|
||||
corner_radius_tl: c.corner_radius[0],
|
||||
|
|
@ -2088,6 +2264,7 @@ impl VulkanRenderer {
|
|||
pos: c.target,
|
||||
tex_pos: c.source,
|
||||
alpha: c.alpha,
|
||||
discard_threshold: c.discard_alpha,
|
||||
size_x: c.size[0],
|
||||
size_y: c.size[1],
|
||||
corner_radius_tl: c.corner_radius[0],
|
||||
|
|
@ -2109,10 +2286,8 @@ impl VulkanRenderer {
|
|||
}
|
||||
}
|
||||
VulkanOp::BlurBarrier(blur) => {
|
||||
// Blur is only meaningful in the FrameBuffer pass.
|
||||
if pass != RenderPass::FrameBuffer {
|
||||
continue;
|
||||
}
|
||||
// BlurBarrier is pushed to exactly one pass in convert_ops
|
||||
// (BB if present, else FB), so no per-pass gating is needed.
|
||||
// End the current dynamic render pass, run the blur work
|
||||
// (image-blit cascade between scratch images), and resume
|
||||
// the render pass with LOAD so subsequent draws layer on
|
||||
|
|
@ -2132,14 +2307,55 @@ impl VulkanRenderer {
|
|||
threshold: m.threshold,
|
||||
_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(
|
||||
buf,
|
||||
target,
|
||||
rect_arr,
|
||||
blur.passes,
|
||||
blur.offset,
|
||||
&mut local_blur_scratch,
|
||||
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);
|
||||
// Pipeline state is invalidated across the render-pass
|
||||
// break — force re-bind on next draw.
|
||||
|
|
@ -2679,6 +2895,7 @@ impl VulkanRenderer {
|
|||
let width = fb.width as f32;
|
||||
let height = fb.height as f32;
|
||||
let mut tag = 0;
|
||||
let mut blur_rects: Vec<Rect> = Vec::new();
|
||||
for opt in opts.iter().rev() {
|
||||
let (opaque, fb_rect) = match opt {
|
||||
GfxApiOpt::Sync => continue,
|
||||
|
|
@ -2706,7 +2923,10 @@ impl VulkanRenderer {
|
|||
(false, rf.rect)
|
||||
}
|
||||
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() {
|
||||
tag |= 1;
|
||||
|
|
@ -2719,6 +2939,20 @@ impl VulkanRenderer {
|
|||
}
|
||||
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 opaque_region = Region::from_rects2(&memory.regions_1);
|
||||
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 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_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] =
|
||||
include_bytes!("shaders_bin/legacy_rounded_fill.vert.spv");
|
||||
pub const LEGACY_ROUNDED_FILL_FRAG: &[u8] =
|
||||
|
|
@ -69,6 +72,7 @@ unsafe impl Packed for TexVertex {}
|
|||
pub struct TexPushConstants {
|
||||
pub vertices: DeviceAddress,
|
||||
pub alpha: f32,
|
||||
pub discard_threshold: f32,
|
||||
}
|
||||
|
||||
unsafe impl Packed for TexPushConstants {}
|
||||
|
|
@ -109,6 +113,7 @@ pub struct LegacyTexPushConstants {
|
|||
pub pos: [[f32; 2]; 4],
|
||||
pub tex_pos: [[f32; 2]; 4],
|
||||
pub alpha: f32,
|
||||
pub discard_threshold: f32,
|
||||
}
|
||||
|
||||
unsafe impl Packed for LegacyTexPushConstants {}
|
||||
|
|
@ -148,6 +153,7 @@ unsafe impl Packed for LegacyRoundedFillPushConstants {}
|
|||
pub struct RoundedTexPushConstants {
|
||||
pub vertices: DeviceAddress,
|
||||
pub alpha: f32,
|
||||
pub discard_threshold: f32,
|
||||
pub size_x: f32,
|
||||
pub size_y: f32,
|
||||
pub corner_radius_tl: f32,
|
||||
|
|
@ -165,6 +171,7 @@ pub struct LegacyRoundedTexPushConstants {
|
|||
pub pos: [[f32; 2]; 4],
|
||||
pub tex_pos: [[f32; 2]; 4],
|
||||
pub alpha: f32,
|
||||
pub discard_threshold: f32,
|
||||
pub size_x: f32,
|
||||
pub size_y: f32,
|
||||
pub corner_radius_tl: f32,
|
||||
|
|
@ -195,6 +202,15 @@ pub struct 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 {
|
||||
pub(super) fn create_shader(
|
||||
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
|
||||
65acbe7a6496279fa22f520ad2036d3e14a7cb1707c6a509ce7858adc4a2dcba src/gfx_apis/vulkan/shaders/blur.vert
|
||||
16ad6f1eb029ccce5e0204a7d79709b05a8a708133feaf8bb20a24371de25ed7 src/gfx_apis/vulkan/shaders/blur_composite.frag
|
||||
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
|
||||
8a38df18851cd13884499820f26939fb7319f45d913d867f254d8118d59fb117 src/gfx_apis/vulkan/shaders/fill.common.glsl
|
||||
21c488d12aa5ad2f109ec44cb856dfe837e02ea9025b5ed64439d742c17cbf30 src/gfx_apis/vulkan/shaders/fill.frag
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue