From a34bdccc06da0f094a2c51e5084cdb3d81bfabcb Mon Sep 17 00:00:00 2001 From: chyyran Date: Wed, 11 Jan 2023 19:44:13 -0500 Subject: [PATCH] vk: implement framebuffer operations (gen_mips/copy/clear) --- .../src/gl/gl3/framebuffer.rs | 8 +- librashader-runtime-vk/src/filter_chain.rs | 17 +- librashader-runtime-vk/src/framebuffer.rs | 5 +- librashader-runtime-vk/src/texture.rs | 325 ++++++++++++++++-- 4 files changed, 326 insertions(+), 29 deletions(-) diff --git a/librashader-runtime-gl/src/gl/gl3/framebuffer.rs b/librashader-runtime-gl/src/gl/gl3/framebuffer.rs index dbd4ecc..61d36d2 100644 --- a/librashader-runtime-gl/src/gl/gl3/framebuffer.rs +++ b/librashader-runtime-gl/src/gl/gl3/framebuffer.rs @@ -70,9 +70,13 @@ impl FramebufferInterface for Gl3Framebuffer { .size .scale_viewport(scaling, viewport.output.size); - if fb.size != size { + if fb.size != size || (mipmap && fb.max_levels == 1) || (!mipmap && fb.max_levels != 1) { fb.size = size; - + if mipmap { + fb.max_levels = u32::MAX; + } else { + fb.max_levels = 1 + } Self::init( fb, size, diff --git a/librashader-runtime-vk/src/filter_chain.rs b/librashader-runtime-vk/src/filter_chain.rs index 17ea60f..52f59d7 100644 --- a/librashader-runtime-vk/src/filter_chain.rs +++ b/librashader-runtime-vk/src/filter_chain.rs @@ -444,7 +444,12 @@ impl FilterChainVulkan { /// Process a frame with the input image. /// - /// When this frame returns, GL_FRAMEBUFFER is bound to 0. + /// * The input image must be in the `VK_SHADER_READ_ONLY_OPTIMAL`. + /// * The output image must be in `VK_COLOR_ATTACHMENT_OPTIMAL`. + /// + /// librashader **will not** create a pipeline barrier for the final pass. The output image will + /// remain in `VK_COLOR_ATTACHMENT_OPTIMAL` after all shader passes. The caller must transition + /// the output image to the final layout. pub fn frame( &mut self, count: usize, @@ -504,6 +509,8 @@ impl FilterChainVulkan { &viewport.output.size, &original, &source, + // todo: need to check **next** + pass.config.mipmap_input )?; } @@ -526,8 +533,12 @@ impl FilterChainVulkan { pass.draw(cmd, index, &self.common, count as u32, 0, viewport, &original, &source, &out)?; - // for second to last pass, we want to transition to copy instead. - out.output.end_pass(cmd); + + if target.max_miplevels > 1 { + target.generate_mipmaps_and_end_pass(cmd); + } else { + out.output.end_pass(cmd); + } source = target.as_input(pass.config.filter, pass.config.wrap_mode)?; let prev_frame_output = self.common diff --git a/librashader-runtime-vk/src/framebuffer.rs b/librashader-runtime-vk/src/framebuffer.rs index 1b6b334..86c5237 100644 --- a/librashader-runtime-vk/src/framebuffer.rs +++ b/librashader-runtime-vk/src/framebuffer.rs @@ -2,7 +2,6 @@ use crate::{error, util}; use crate::filter_chain::Vulkan; use crate::texture::{VulkanImage}; use ash::vk; -use ash::vk::{ImageAspectFlags, ImageViewType}; use librashader_common::Size; #[derive(Clone)] @@ -21,7 +20,7 @@ impl OutputFramebuffer { .base_array_layer(0) .level_count(1) .layer_count(1) - .aspect_mask(ImageAspectFlags::COLOR) + .aspect_mask(vk::ImageAspectFlags::COLOR) .build(); let swizzle_components = vk::ComponentMapping::builder() @@ -32,7 +31,7 @@ impl OutputFramebuffer { .build(); let mut view_info = vk::ImageViewCreateInfo::builder() - .view_type(ImageViewType::TYPE_2D) + .view_type(vk::ImageViewType::TYPE_2D) .format(image.format) .image(image.image.clone()) .subresource_range(image_subresource) diff --git a/librashader-runtime-vk/src/texture.rs b/librashader-runtime-vk/src/texture.rs index 39d1c2b..31d0fa0 100644 --- a/librashader-runtime-vk/src/texture.rs +++ b/librashader-runtime-vk/src/texture.rs @@ -3,10 +3,7 @@ use crate::filter_chain::Vulkan; use crate::util::find_vulkan_memory_type; use crate::vulkan_primitives::VulkanImageMemory; use ash::vk; -use ash::vk::{ - ImageAspectFlags, ImageLayout, ImageTiling, ImageType, ImageUsageFlags, ImageViewType, - SampleCountFlags, SharingMode, -}; + use librashader_common::{FilterMode, ImageFormat, Size, WrapMode}; use librashader_presets::Scale2D; use librashader_runtime::scaling::{MipmapSize, ViewportSize}; @@ -18,6 +15,7 @@ pub struct OwnedTexture { pub image: VulkanImage, pub memory: VulkanImageMemory, pub max_miplevels: u32, + pub levels: u32, } impl OwnedTexture { @@ -29,22 +27,22 @@ impl OwnedTexture { max_miplevels: u32, ) -> error::Result { let image_create_info = vk::ImageCreateInfo::builder() - .image_type(ImageType::TYPE_2D) + .image_type(vk::ImageType::TYPE_2D) .format(format.into()) .extent(size.into()) .mip_levels(std::cmp::min(max_miplevels, size.calculate_miplevels())) .array_layers(1) - .samples(SampleCountFlags::TYPE_1) - .tiling(ImageTiling::OPTIMAL) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::OPTIMAL) .flags(vk::ImageCreateFlags::MUTABLE_FORMAT) .usage( - ImageUsageFlags::SAMPLED - | ImageUsageFlags::COLOR_ATTACHMENT - | ImageUsageFlags::TRANSFER_DST - | ImageUsageFlags::TRANSFER_SRC, + vk::ImageUsageFlags::SAMPLED + | vk::ImageUsageFlags::COLOR_ATTACHMENT + | vk::ImageUsageFlags::TRANSFER_DST + | vk::ImageUsageFlags::TRANSFER_SRC, ) - .sharing_mode(SharingMode::EXCLUSIVE) - .initial_layout(ImageLayout::UNDEFINED) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .initial_layout(vk::ImageLayout::UNDEFINED) .build(); let image = unsafe { device.create_image(&image_create_info, None)? }; @@ -68,7 +66,7 @@ impl OwnedTexture { .base_array_layer(0) .level_count(image_create_info.mip_levels) .layer_count(1) - .aspect_mask(ImageAspectFlags::COLOR) + .aspect_mask(vk::ImageAspectFlags::COLOR) .build(); let swizzle_components = vk::ComponentMapping::builder() @@ -79,7 +77,7 @@ impl OwnedTexture { .build(); let mut view_info = vk::ImageViewCreateInfo::builder() - .view_type(ImageViewType::TYPE_2D) + .view_type(vk::ImageViewType::TYPE_2D) .format(format.into()) .image(image.clone()) .subresource_range(image_subresource) @@ -99,6 +97,7 @@ impl OwnedTexture { }, memory, max_miplevels, + levels: std::cmp::min(max_miplevels, size.calculate_miplevels()) }) } @@ -118,17 +117,21 @@ impl OwnedTexture { viewport_size: &Size, _original: &InputTexture, source: &InputTexture, + mipmap: bool, ) -> error::Result> { - - let size = source.image.size.scale_viewport(scaling, *viewport_size); + if self.image.size != size || (mipmap && self.max_miplevels == 1) || (!mipmap && self.max_miplevels != 1) { + let max_levels = if mipmap { + u32::MAX + } else { + 1 + }; - if self.image.size != size { let mut new = OwnedTexture::new_internal(self.device.clone(), self.mem_props, size, if format == ImageFormat::Unknown { ImageFormat::R8G8B8A8Unorm } else { format - }, self.max_miplevels)?; + }, max_levels)?; let old = std::mem::replace(self, new); drop(old) @@ -143,7 +146,7 @@ impl OwnedTexture { .base_array_layer(0) .level_count(1) .layer_count(1) - .aspect_mask(ImageAspectFlags::COLOR) + .aspect_mask(vk::ImageAspectFlags::COLOR) .build(); let swizzle_components = vk::ComponentMapping::builder() @@ -154,7 +157,7 @@ impl OwnedTexture { .build(); let mut view_info = vk::ImageViewCreateInfo::builder() - .view_type(ImageViewType::TYPE_2D) + .view_type(vk::ImageViewType::TYPE_2D) .format(self.image.format) .image(self.image.image.clone()) .subresource_range(image_subresource) @@ -174,6 +177,286 @@ impl OwnedTexture { mip_filter: filter, }) } + + pub fn generate_mipmaps_and_end_pass(&self, cmd: vk::CommandBuffer) { + let input_barrier = vk::ImageMemoryBarrier::builder() + .src_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE) + .dst_access_mask(vk::AccessFlags::TRANSFER_READ) + .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(self.image.image) + .subresource_range(vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: vk::REMAINING_ARRAY_LAYERS + }) + .build(); + + + let mipchain_barrier = vk::ImageMemoryBarrier::builder() + .src_access_mask(vk::AccessFlags::empty()) + .dst_access_mask(vk::AccessFlags::TRANSFER_WRITE) + .old_layout(vk::ImageLayout::UNDEFINED) + .new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(self.image.image) + .subresource_range(vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 1, + base_array_layer: 0, + level_count: vk::REMAINING_MIP_LEVELS, + layer_count: vk::REMAINING_ARRAY_LAYERS + }) + .build(); + + unsafe { + self.device.cmd_pipeline_barrier(cmd, + vk::PipelineStageFlags::ALL_GRAPHICS, + vk::PipelineStageFlags::TRANSFER, + vk::DependencyFlags::empty(), + &[], + &[], + &[input_barrier, mipchain_barrier] + ); + + for level in 1..self.levels { + // need to transition from DST to SRC, one level at a time. + if level > 1 { + + let next_barrier = vk::ImageMemoryBarrier::builder() + .src_access_mask(vk::AccessFlags::TRANSFER_WRITE) + .dst_access_mask(vk::AccessFlags::TRANSFER_READ) + .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) + .new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(self.image.image) + .subresource_range(vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: level - 1, + base_array_layer: 0, + level_count: 1, + layer_count: vk::REMAINING_ARRAY_LAYERS + }) + .build(); + + self.device.cmd_pipeline_barrier(cmd, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::TRANSFER, + vk::DependencyFlags::empty(), + &[], + &[], + &[next_barrier] + ); + } + + let source_size = self.image.size.scale_mipmap(level - 1); + let target_size = self.image.size.scale_mipmap(level); + + let src_offsets = [ + vk::Offset3D { x: 0, y: 0, z: 0 }, + vk::Offset3D { + x: source_size.width as i32, + y: source_size.height as i32, + z: 1, + }, + ]; + + let dst_offsets = [ + vk::Offset3D { x: 0, y: 0, z: 0 }, + vk::Offset3D { + x: target_size.width as i32, + y: target_size.height as i32, + z: 1, + }, + ]; + + let src_subresource = vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(level - 1) + .base_array_layer(0) + .layer_count(1) + .build(); + + let dst_subresource = vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(level) + .base_array_layer(0) + .layer_count(1) + .build(); + + let image_blit = [vk::ImageBlit::builder() + .src_subresource(src_subresource) + .src_offsets(src_offsets) + .dst_subresource(dst_subresource) + .dst_offsets(dst_offsets) + .build()]; + + self.device.cmd_blit_image( + cmd, + self.image.image, + vk::ImageLayout::TRANSFER_SRC_OPTIMAL, + self.image.image, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + &image_blit, + vk::Filter::LINEAR, + ); + } + + // move everything to SHADER_READ_ONLY_OPTIMAL + + let input_barrier = vk::ImageMemoryBarrier::builder() + .src_access_mask(vk::AccessFlags::TRANSFER_READ) + .dst_access_mask(vk::AccessFlags::SHADER_READ) + .old_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) + .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(self.image.image) + .subresource_range(vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: self.levels - 1, + base_array_layer: 0, + layer_count: vk::REMAINING_ARRAY_LAYERS + }) + .build(); + + + let mipchain_barrier = vk::ImageMemoryBarrier::builder() + .src_access_mask(vk::AccessFlags::TRANSFER_WRITE) + .dst_access_mask(vk::AccessFlags::SHADER_READ) + .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) + .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(self.image.image) + .subresource_range(vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: self.levels - 1, + base_array_layer: 0, + level_count: 1, + layer_count: vk::REMAINING_ARRAY_LAYERS + }) + .build(); + + // next past waits for ALL_GRAPHICS, use dependency chain and FRAGMENT_SHADER dst stage + // to ensure that next pass doesn't start until mipchain is complete. + self.device.cmd_pipeline_barrier(cmd, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::FRAGMENT_SHADER, + vk::DependencyFlags::empty(), + &[], + &[], + &[input_barrier, mipchain_barrier] + ); + } + } + + /// SAFETY: self must fit the source image + pub unsafe fn copy_from(&self, cmd: vk::CommandBuffer, source: VulkanImage, source_layout: vk::ImageLayout) { + let region = vk::ImageCopy::builder() + .src_subresource(vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(0) + .base_array_layer(0) + .layer_count(1) + .build() + ) + .dst_subresource(vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(0) + .base_array_layer(0) + .layer_count(1) + .build() + ) + .src_offset(Default::default()) + .dst_offset(Default::default()) + .extent(source.size.into()) + .build(); + + unsafe { + util::vulkan_image_layout_transition_levels(&self.device, + cmd, + self.image.image, + vk::REMAINING_MIP_LEVELS, + vk::ImageLayout::UNDEFINED, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + vk::AccessFlags::empty(), + vk::AccessFlags::TRANSFER_WRITE, + vk::PipelineStageFlags::FRAGMENT_SHADER, + vk::PipelineStageFlags::TRANSFER, + vk::QUEUE_FAMILY_IGNORED, vk::QUEUE_FAMILY_IGNORED + ); + + self.device.cmd_copy_image(cmd, source.image, source_layout, self.image.image, vk::ImageLayout::TRANSFER_DST_OPTIMAL, &[region]); + util::vulkan_image_layout_transition_levels(&self.device, + cmd, + self.image.image, + vk::REMAINING_MIP_LEVELS, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, + vk::AccessFlags::TRANSFER_WRITE, + vk::AccessFlags::SHADER_READ, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::FRAGMENT_SHADER, + vk::QUEUE_FAMILY_IGNORED, + vk::QUEUE_FAMILY_IGNORED + ); + } + } + + pub fn clear(&self, cmd: vk::CommandBuffer) { + unsafe { + util::vulkan_image_layout_transition_levels(&self.device, + cmd, + self.image.image, + vk::REMAINING_MIP_LEVELS, + vk::ImageLayout::UNDEFINED, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + vk::AccessFlags::empty(), + vk::AccessFlags::TRANSFER_WRITE, + vk::PipelineStageFlags::TOP_OF_PIPE, + vk::PipelineStageFlags::TRANSFER, + vk::QUEUE_FAMILY_IGNORED, + vk::QUEUE_FAMILY_IGNORED + ); + self.device.cmd_clear_color_image(cmd, + self.image.image, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + &vk::ClearColorValue { + float32: [0.0, 0.0, 0.0, 0.0] + }, + &[vk::ImageSubresourceRange::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1) + .build()] + + ); + + util::vulkan_image_layout_transition_levels(&self.device, + cmd, + self.image.image, + vk::REMAINING_MIP_LEVELS, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, + vk::AccessFlags::TRANSFER_WRITE, + vk::AccessFlags::SHADER_READ, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::FRAGMENT_SHADER, + vk::QUEUE_FAMILY_IGNORED, + vk::QUEUE_FAMILY_IGNORED + ); + } + } } impl Drop for OwnedTexture {