544 lines
21 KiB
Rust
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(())
|
|
}
|
|
}
|