Add blur to vulkan and opengl render pipelines
This commit is contained in:
parent
6d3bff952e
commit
0701c4e4cf
41 changed files with 1990 additions and 47 deletions
544
src/gfx_apis/vulkan/blur.rs
Normal file
544
src/gfx_apis/vulkan/blur.rs
Normal file
|
|
@ -0,0 +1,544 @@
|
|||
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(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue