1
0
Fork 0
forked from wry/wry
wry/src/gfx_apis/vulkan/blur.rs

544 lines
21 KiB
Rust

use {
crate::gfx_apis::vulkan::{
VulkanError,
image::{QueueFamily, QueueState, VulkanImage, VulkanImageMemory},
renderer::VulkanRenderer,
shaders::BlurCompositePushConstants,
},
ash::vk::{
AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BlitImageInfo2, CommandBuffer,
DependencyInfoKHR, DescriptorImageInfo, DescriptorType, Extent2D, Extent3D, Filter,
ImageAspectFlags, ImageBlit2, ImageCreateInfo, ImageLayout, ImageMemoryBarrier2,
ImageSubresourceLayers, ImageSubresourceRange, ImageTiling, ImageType, ImageUsageFlags,
ImageViewCreateInfo, ImageViewType, Offset2D, Offset3D, PipelineBindPoint,
PipelineStageFlags2, Rect2D, RenderingAttachmentInfo, RenderingInfo, SampleCountFlags,
ShaderStageFlags, SharingMode, Viewport, WriteDescriptorSet,
},
gpu_alloc::UsageFlags,
run_on_drop::on_drop,
std::{cell::Cell, collections::hash_map::Entry, 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(),
);
pub(super) struct BlurMaskRecord<'a> {
pub(super) mask_view: ash::vk::ImageView,
pub(super) mask_source_points: [[f32; 2]; 4],
pub(super) target_points: [[f32; 2]; 4],
pub(super) threshold: f32,
pub(super) _phantom: std::marker::PhantomData<&'a ()>,
}
impl VulkanRenderer {
fn acquire_blur_scratch(
self: &Rc<Self>,
width: u32,
height: u32,
format: ash::vk::Format,
) -> 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);
}
let create_info = ImageCreateInfo::default()
.image_type(ImageType::TYPE_2D)
.format(format)
.mip_levels(1)
.array_layers(1)
.tiling(ImageTiling::OPTIMAL)
.samples(SampleCountFlags::TYPE_1)
.sharing_mode(SharingMode::EXCLUSIVE)
.initial_layout(ImageLayout::UNDEFINED)
.extent(Extent3D {
width,
height,
depth: 1,
})
.usage(BLUR_SCRATCH_USAGE);
let image = unsafe { self.device.device.create_image(&create_info, None) };
let image = image.map_err(VulkanError::CreateImage)?;
let destroy_image = on_drop(|| unsafe { self.device.device.destroy_image(image, None) });
let memory_requirements =
unsafe { self.device.device.get_image_memory_requirements(image) };
let allocation =
self.allocator
.alloc(&memory_requirements, UsageFlags::FAST_DEVICE_ACCESS, false)?;
let res = unsafe {
self.device
.device
.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)
.view_type(ImageViewType::TYPE_2D)
.subresource_range(ImageSubresourceRange {
aspect_mask: ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
});
let view = unsafe {
self.device
.device
.create_image_view(&image_view_create_info, None)
};
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,
width,
height,
stride: 0,
texture_view: view,
render_view: None,
image,
is_undefined: Cell::new(true),
contents_are_undefined: Cell::new(true),
queue_state: Cell::new(QueueState::Acquired {
family: QueueFamily::Gfx,
}),
ty: VulkanImageMemory::Blend(allocation),
bridge: None,
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));
}
}
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).
pub(super) fn record_blur(
self: &Rc<Self>,
buf: CommandBuffer,
target: &VulkanImage,
rect: [i32; 4],
passes: u8,
scratch_out: &mut Vec<Rc<VulkanImage>>,
mask: Option<&BlurMaskRecord<'_>>,
) -> Result<(), VulkanError> {
let [x1, y1, x2, y2] = rect;
let x1 = x1.max(0).min(target.width as i32);
let y1 = y1.max(0).min(target.height as i32);
let x2 = x2.max(0).min(target.width as i32);
let y2 = y2.max(0).min(target.height as i32);
let w = (x2 - x1) as u32;
let h = (y2 - y1) as u32;
if w < 4 || h < 4 {
return Ok(());
}
let passes = passes.clamp(1, 6) as u32;
let format = target.format.vk_format;
let mut levels: Vec<Rc<VulkanImage>> =
Vec::with_capacity(passes as usize + 1);
levels.push(self.acquire_blur_scratch(w, h, format)?);
let mut cw = w;
let mut ch = h;
for _ in 0..passes {
cw = (cw / 2).max(1);
ch = (ch / 2).max(1);
levels.push(self.acquire_blur_scratch(cw, ch, format)?);
}
let dev = &self.device.device;
let subres = ImageSubresourceLayers::default()
.aspect_mask(ImageAspectFlags::COLOR)
.layer_count(1)
.base_array_layer(0)
.mip_level(0);
let subres_range = ImageSubresourceRange {
aspect_mask: ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
};
let barrier = |image,
old: ImageLayout,
new: ImageLayout,
src_stage: PipelineStageFlags2,
src_access: AccessFlags2,
dst_stage: PipelineStageFlags2,
dst_access: AccessFlags2| {
ImageMemoryBarrier2::default()
.image(image)
.old_layout(old)
.new_layout(new)
.subresource_range(subres_range)
.src_stage_mask(src_stage)
.src_access_mask(src_access)
.dst_stage_mask(dst_stage)
.dst_access_mask(dst_access)
.src_queue_family_index(ash::vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(ash::vk::QUEUE_FAMILY_IGNORED)
};
let do_barriers = |barriers: &[ImageMemoryBarrier2]| unsafe {
let dep = DependencyInfoKHR::default().image_memory_barriers(barriers);
dev.cmd_pipeline_barrier2(buf, &dep);
};
// Step 1: target COLOR_ATTACHMENT -> TRANSFER_SRC.
// Step 1: levels[0] UNDEFINED -> TRANSFER_DST.
do_barriers(&[
barrier(
target.image,
ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
ImageLayout::TRANSFER_SRC_OPTIMAL,
PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
AccessFlags2::COLOR_ATTACHMENT_WRITE,
PipelineStageFlags2::TRANSFER,
AccessFlags2::TRANSFER_READ,
),
barrier(
levels[0].image,
ImageLayout::UNDEFINED,
ImageLayout::TRANSFER_DST_OPTIMAL,
PipelineStageFlags2::TOP_OF_PIPE,
AccessFlags2::empty(),
PipelineStageFlags2::TRANSFER,
AccessFlags2::TRANSFER_WRITE,
),
]);
// Step 2: blit target rect -> levels[0] full.
let blit = ImageBlit2::default()
.src_subresource(subres)
.dst_subresource(subres)
.src_offsets([
Offset3D { x: x1, y: y1, z: 0 },
Offset3D { x: x2, y: y2, z: 1 },
])
.dst_offsets([
Offset3D { x: 0, y: 0, z: 0 },
Offset3D {
x: w as i32,
y: h as i32,
z: 1,
},
]);
let blit_info = BlitImageInfo2::default()
.src_image(target.image)
.src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL)
.dst_image(levels[0].image)
.dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL)
.filter(Filter::LINEAR)
.regions(slice::from_ref(&blit));
unsafe {
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));
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,
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,
),
]);
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,
},
};
// Identity uv across blurred level 0 (full image is the blurred rect).
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(levels[0].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);
}
} else {
// Final blit: levels[0] -> target rect.
do_barriers(&[
barrier(
levels[0].image,
ImageLayout::TRANSFER_DST_OPTIMAL,
ImageLayout::TRANSFER_SRC_OPTIMAL,
PipelineStageFlags2::TRANSFER,
AccessFlags2::TRANSFER_WRITE,
PipelineStageFlags2::TRANSFER,
AccessFlags2::TRANSFER_READ,
),
barrier(
target.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: w as i32,
y: h as i32,
z: 1,
},
])
.dst_offsets([
Offset3D { x: x1, y: y1, z: 0 },
Offset3D { x: x2, y: y2, z: 1 },
]);
let blit_info = BlitImageInfo2::default()
.src_image(levels[0].image)
.src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL)
.dst_image(target.image)
.dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL)
.filter(Filter::NEAREST)
.regions(slice::from_ref(&blit));
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,
)]);
}
// Hold the scratch images until the frame is submitted.
scratch_out.extend(levels);
Ok(())
}
}