@ -52,6 +52,9 @@ using VideoCore::Surface::IsPixelFormatInteger;
using VideoCore : : Surface : : SurfaceType ;
namespace {
constexpr bool ENABLE_MSAA_RESOLVE_CONSUME = true ;
constexpr bool ENABLE_MSAA_COLOR_DISCARD = true ;
constexpr VkBorderColor ConvertBorderColor ( const std : : array < float , 4 > & color ) {
if ( color = = std : : array < float , 4 > { 0 , 0 , 0 , 0 } ) {
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK ;
@ -909,9 +912,6 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
astc_decoder_pass . emplace ( device , scheduler , descriptor_pool , staging_buffer_pool ,
compute_pass_descriptor_queue , memory_allocator ) ;
}
if ( device . IsStorageImageMultisampleSupported ( ) ) {
msaa_copy_pass . emplace ( device , scheduler , descriptor_pool , staging_buffer_pool , compute_pass_descriptor_queue ) ;
}
if ( ! device . IsKhrImageFormatListSupported ( ) ) {
return ;
}
@ -995,12 +995,84 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) {
return * buffers [ level ] ;
}
VkImageView TextureCacheRuntime : : GetOrCreateResolveShadow ( VkImage msaa_image , VkFormat format ,
VkExtent2D extent , u32 layers ) {
ResolveShadow & shadow = resolve_shadows [ msaa_image ] ;
if ( shadow . image & & shadow . format = = format & & shadow . extent . width = = extent . width & &
shadow . extent . height = = extent . height & & shadow . layers = = layers ) {
shadow . up_to_date = true ;
return * shadow . view ;
}
shadow . image = memory_allocator . CreateImage ( VkImageCreateInfo {
. sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO ,
. pNext = nullptr ,
. flags = 0 ,
. imageType = VK_IMAGE_TYPE_2D ,
. format = format ,
. extent = { extent . width , extent . height , 1 } ,
. mipLevels = 1 ,
. arrayLayers = layers ,
. samples = VK_SAMPLE_COUNT_1_BIT ,
. tiling = VK_IMAGE_TILING_OPTIMAL ,
. usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT ,
. sharingMode = VK_SHARING_MODE_EXCLUSIVE ,
. queueFamilyIndexCount = 0 ,
. pQueueFamilyIndices = nullptr ,
. initialLayout = VK_IMAGE_LAYOUT_UNDEFINED ,
} ) ;
shadow . view = device . GetLogical ( ) . CreateImageView ( VkImageViewCreateInfo {
. sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO ,
. pNext = nullptr ,
. flags = 0 ,
. image = * shadow . image ,
. viewType = layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D ,
. format = format ,
. components { } ,
. subresourceRange {
. aspectMask = VK_IMAGE_ASPECT_COLOR_BIT ,
. baseMipLevel = 0 ,
. levelCount = 1 ,
. baseArrayLayer = 0 ,
. layerCount = layers ,
} ,
} ) ;
shadow . format = format ;
shadow . extent = extent ;
shadow . layers = layers ;
shadow . up_to_date = true ;
return * shadow . view ;
}
const TextureCacheRuntime : : ResolveShadow * TextureCacheRuntime : : GetValidResolveShadow (
VkImage msaa_image ) const {
const auto it = resolve_shadows . find ( msaa_image ) ;
if ( it = = resolve_shadows . end ( ) | | ! it - > second . up_to_date | | ! it - > second . image ) {
return nullptr ;
}
return & it - > second ;
}
void TextureCacheRuntime : : InvalidateResolveShadow ( VkImage msaa_image ) {
const auto it = resolve_shadows . find ( msaa_image ) ;
if ( it ! = resolve_shadows . end ( ) ) {
it - > second . up_to_date = false ;
}
}
void TextureCacheRuntime : : EraseResolveShadow ( VkImage msaa_image ) {
resolve_shadows . erase ( msaa_image ) ;
}
void TextureCacheRuntime : : BarrierFeedbackLoop ( ) {
scheduler . RequestOutsideRenderPassOperationContext ( ) ;
}
void TextureCacheRuntime : : ReinterpretImage ( Image & dst , Image & src ,
std : : span < const VideoCommon : : ImageCopy > copies ) {
if ( ENABLE_MSAA_RESOLVE_CONSUME ) {
InvalidateResolveShadow ( dst . Handle ( ) ) ;
}
boost : : container : : small_vector < VkBufferImageCopy , 16 > vk_in_copies ( copies . size ( ) ) ;
boost : : container : : small_vector < VkBufferImageCopy , 16 > vk_out_copies ( copies . size ( ) ) ;
const VkImageAspectFlags src_aspect_mask = src . AspectMask ( ) ;
@ -1147,6 +1219,15 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
return ;
}
ASSERT ( src . format = = dst . format ) ;
if ( is_src_msaa & & ! is_dst_msaa & &
( aspect_mask & ( VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT ) ) ! = 0 ) {
if ( ( aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT ) = = 0 ) {
UNIMPLEMENTED_MSG ( " Stencil-only MSAA resolve is not supported " ) ;
return ;
}
blit_image_helper . ResolveDepthStencil ( dst_framebuffer , src , dst_region , src_region ) ;
return ;
}
if ( aspect_mask = = ( VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT ) ) {
const auto format = src . format ;
const auto can_blit_depth_stencil = [ this , format ] {
@ -1172,19 +1253,20 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
ASSERT ( ! ( is_dst_msaa & & ! is_src_msaa ) ) ;
ASSERT ( operation = = Fermi2D : : Operation : : SrcCopy ) ;
const VkImage dst_image = dst . ImageHandle ( ) ;
const VkImage src_image = src . ImageHandle ( ) ;
const VkImageSubresourceLayers dst_layers = MakeSubresourceLayers ( & dst ) ;
const VkImageSubresourceLayers src_layers = MakeSubresourceLayers ( & src ) ;
const bool is_msaa_to_msaa = is_src_msaa & & is_dst_msaa ;
// NVIDIA 510+ and Intel crash on MSAA->MSAA blits (scaling operations)
// Fall back to 3D helpers for MSAA scaling
if ( is_msaa_to_msaa & & aspect_mask = = VK_IMAGE_ASPECT_COLOR_BIT ) {
blit_image_helper . BlitColorMSAA ( dst_framebuffer , src , dst_region , src_region ) ;
return ;
}
if ( is_msaa_to_msaa & & device . CantBlitMSAA ( ) ) {
// This should be handled by NeedsScaleHelper() and use 3D helpers instead
UNIMPLEMENTED_MSG ( " MSAA to MSAA blit not supported on this driver " ) ;
UNIMPLEMENTED_MSG ( " MSAA to MSAA depth-stencil blit is not supported on this driver " ) ;
return ;
}
const VkImage dst_image = dst . ImageHandle ( ) ;
const VkImage src_image = src . ImageHandle ( ) ;
const VkImageSubresourceLayers dst_layers = MakeSubresourceLayers ( & dst ) ;
const VkImageSubresourceLayers src_layers = MakeSubresourceLayers ( & src ) ;
const bool is_resolve = is_src_msaa & & ! is_dst_msaa ;
scheduler . RequestOutsideRenderPassOperationContext ( ) ;
scheduler . Record ( [ filter , dst_region , src_region , dst_image , src_image , dst_layers , src_layers ,
@ -1442,6 +1524,9 @@ bool TextureCacheRuntime::IsFormatScalable(PixelFormat format) {
void TextureCacheRuntime : : CopyImage ( Image & dst , Image & src ,
std : : span < const VideoCommon : : ImageCopy > copies ) {
if ( ENABLE_MSAA_RESOLVE_CONSUME ) {
InvalidateResolveShadow ( dst . Handle ( ) ) ;
}
// As per the size-compatible formats section of vulkan, copy manually via ReinterpretImage
// these images that aren't size-compatible
if ( BytesPerBlock ( src . info . format ) ! = BytesPerBlock ( dst . info . format ) ) {
@ -1555,10 +1640,117 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
void TextureCacheRuntime : : CopyImageMSAA ( Image & dst , Image & src ,
std : : span < const VideoCommon : : ImageCopy > copies ) {
const bool msaa_to_non_msaa = src . info . num_samples > 1 & & dst . info . num_samples = = 1 ;
if ( msaa_copy_pass ) {
return msaa_copy_pass - > CopyImage ( dst , src , copies , msaa_to_non_msaa ) ;
const u32 num_samples = msaa_to_non_msaa ? src . info . num_samples : dst . info . num_samples ;
if ( dst . AspectMask ( ) ! = VK_IMAGE_ASPECT_COLOR_BIT | |
VideoCore : : Surface : : IsPixelFormatInteger ( dst . info . format ) ) {
UNIMPLEMENTED_MSG ( " Copying images with different samples is not supported. " ) ;
return ;
}
if ( ENABLE_MSAA_RESOLVE_CONSUME & & msaa_to_non_msaa & & copies . size ( ) = = 1 & &
src . info . format = = dst . info . format ) {
const VideoCommon : : ImageCopy & copy = copies . front ( ) ;
const ResolveShadow * const shadow = GetValidResolveShadow ( src . Handle ( ) ) ;
if ( shadow ! = nullptr & & copy . src_offset . x = = 0 & & copy . src_offset . y = = 0 & &
copy . src_subresource . base_level = = 0 & &
static_cast < u32 > ( copy . extent . width ) < = shadow - > extent . width & &
static_cast < u32 > ( copy . extent . height ) < = shadow - > extent . height ) {
const VkImage shadow_image = * shadow - > image ;
const VkImage dst_image = dst . Handle ( ) ;
const VkImageCopy region {
. srcSubresource {
. aspectMask = VK_IMAGE_ASPECT_COLOR_BIT ,
. mipLevel = 0 ,
. baseArrayLayer = static_cast < u32 > ( copy . src_subresource . base_layer ) ,
. layerCount = static_cast < u32 > ( copy . src_subresource . num_layers ) ,
} ,
. srcOffset = { 0 , 0 , 0 } ,
. dstSubresource {
. aspectMask = VK_IMAGE_ASPECT_COLOR_BIT ,
. mipLevel = static_cast < u32 > ( copy . dst_subresource . base_level ) ,
. baseArrayLayer = static_cast < u32 > ( copy . dst_subresource . base_layer ) ,
. layerCount = static_cast < u32 > ( copy . dst_subresource . num_layers ) ,
} ,
. dstOffset = { copy . dst_offset . x , copy . dst_offset . y , copy . dst_offset . z } ,
. extent = { copy . extent . width , copy . extent . height , 1 } ,
} ;
scheduler . RequestOutsideRenderPassOperationContext ( ) ;
scheduler . Record ( [ shadow_image , dst_image , region ] ( vk : : CommandBuffer cmdbuf ) {
const std : : array pre_barriers {
VkImageMemoryBarrier {
. sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER ,
. pNext = nullptr ,
. srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT ,
. dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT ,
. oldLayout = VK_IMAGE_LAYOUT_GENERAL ,
. newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL ,
. srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. image = shadow_image ,
. subresourceRange { VK_IMAGE_ASPECT_COLOR_BIT , 0 , VK_REMAINING_MIP_LEVELS , 0 ,
VK_REMAINING_ARRAY_LAYERS } ,
} ,
VkImageMemoryBarrier {
. sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER ,
. pNext = nullptr ,
. srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_TRANSFER_WRITE_BIT ,
. dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT ,
. oldLayout = VK_IMAGE_LAYOUT_GENERAL ,
. newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL ,
. srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. image = dst_image ,
. subresourceRange { VK_IMAGE_ASPECT_COLOR_BIT , 0 , VK_REMAINING_MIP_LEVELS , 0 ,
VK_REMAINING_ARRAY_LAYERS } ,
} ,
} ;
const std : : array post_barriers {
VkImageMemoryBarrier {
. sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER ,
. pNext = nullptr ,
. srcAccessMask = 0 ,
. dstAccessMask = 0 ,
. oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL ,
. newLayout = VK_IMAGE_LAYOUT_GENERAL ,
. srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. image = shadow_image ,
. subresourceRange { VK_IMAGE_ASPECT_COLOR_BIT , 0 , VK_REMAINING_MIP_LEVELS , 0 ,
VK_REMAINING_ARRAY_LAYERS } ,
} ,
VkImageMemoryBarrier {
. sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER ,
. pNext = nullptr ,
. srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT ,
. dstAccessMask = VK_ACCESS_SHADER_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT ,
. oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL ,
. newLayout = VK_IMAGE_LAYOUT_GENERAL ,
. srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. image = dst_image ,
. subresourceRange { VK_IMAGE_ASPECT_COLOR_BIT , 0 , VK_REMAINING_MIP_LEVELS , 0 ,
VK_REMAINING_ARRAY_LAYERS } ,
} ,
} ;
cmdbuf . PipelineBarrier ( VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT ,
VK_PIPELINE_STAGE_TRANSFER_BIT , 0 , nullptr , nullptr ,
pre_barriers ) ;
cmdbuf . CopyImage ( shadow_image , VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL , dst_image ,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL , region ) ;
cmdbuf . PipelineBarrier ( VK_PIPELINE_STAGE_TRANSFER_BIT ,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT , 0 , nullptr , nullptr ,
post_barriers ) ;
} ) ;
return ;
}
}
UNIMPLEMENTED_MSG ( " Copying images with different samples is not supported. " ) ;
blit_image_helper . CopyMSAA ( render_pass_cache , dst . Handle ( ) , dst . info . format , src . Handle ( ) ,
src . info . format , num_samples , copies , msaa_to_non_msaa ) ;
}
u64 TextureCacheRuntime : : GetDeviceLocalMemory ( ) const {
@ -1577,7 +1769,11 @@ std::optional<size_t> TextureCacheRuntime::GetSamplerHeapBudget() const {
return device . GetSamplerHeapBudget ( ) ;
}
void TextureCacheRuntime : : TickFrame ( ) { }
void TextureCacheRuntime : : TickFrame ( ) {
std : : erase_if ( pending_msaa_images , [ this ] ( const auto & pending ) {
return scheduler . IsFree ( pending . first ) ;
} ) ;
}
Image : : Image ( TextureCacheRuntime & runtime_ , const ImageInfo & info_ , GPUVAddr gpu_addr_ ,
VAddr cpu_addr_ )
@ -1632,7 +1828,16 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
Image : : Image ( const VideoCommon : : NullImageParams & params ) : VideoCommon : : ImageBase { params } { }
Image : : ~ Image ( ) = default ;
Image : : ~ Image ( ) {
if ( ENABLE_MSAA_RESOLVE_CONSUME & & runtime ! = nullptr ) {
if ( original_image ) {
runtime - > EraseResolveShadow ( * original_image ) ;
}
if ( scaled_image ) {
runtime - > EraseResolveShadow ( * scaled_image ) ;
}
}
}
void Image : : AllocateComputeUnswizzleBuffer ( u32 max_slices ) {
using VideoCore : : Surface : : BytesPerBlock ;
@ -1679,82 +1884,75 @@ void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) {
void Image : : UploadMemory ( VkBuffer buffer , VkDeviceSize offset ,
std : : span < const VideoCommon : : BufferImageCopy > copies ) {
// TODO: Move this to another API
if ( ENABLE_MSAA_RESOLVE_CONSUME & & runtime ! = nullptr ) {
runtime - > InvalidateResolveShadow ( Handle ( ) ) ;
}
const bool is_rescaled = True ( flags & ImageFlagBits : : Rescaled ) ;
if ( is_rescaled ) {
ScaleDown ( true ) ;
}
// Handle MSAA upload if necessary
/* WARNING, TODO: This code uses some hacks, besides being fundamentally ugly
since tropic didn ' t want to touch it for a long time , so it needs a rewrite from someone
better than me at vulkan . */
// CHANGE: Gate the MSAA path more strictly and only use it for color, when the pass and device
// support are available. Avoid running the MSAA path when prerequisites aren't met,
// preventing validation and runtime issues.
const bool wants_msaa_upload = info . num_samples > 1
& & ( aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT ) ! = 0
& & runtime - > CanUploadMSAA ( ) & & runtime - > msaa_copy_pass . has_value ( )
& & runtime - > device . IsStorageImageMultisampleSupported ( ) ;
& & ! VideoCore : : Surface : : IsPixelFormatInteger ( info . format ) ;
if ( wants_msaa_upload ) {
// Create a temporary non-MSAA image to upload the data first
ImageInfo temp_info = info ;
temp_info . num_samples = 1 ;
// CHANGE: Build a fresh VkImageCreateInfo with robust usage flags for the temp image.
// Using the target image's usage as-is could miss STORAGE/TRANSFER bits and trigger
// validation errors.
VkImageCreateInfo image_ci = MakeImageCreateInfo ( runtime - > device , temp_info ) ;
image_ci . usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT ;
// CHANGE: The previous stack-allocated wrapper was destroyed at function exit,
// which could destroy VkImage before the GPU used it.
auto temp_wrapper = std : : make_shared < Image > ( * runtime , temp_info , 0 , 0 ) ;
temp_wrapper - > original_image = runtime - > memory_allocator . CreateImage ( image_ci ) ;
temp_wrapper - > current_image = & Image : : original_image ;
temp_wrapper - > aspect_mask = aspect_mask ;
temp_wrapper - > initialized = true ;
// Upload to the temporary non-MSAA image
image_ci . format =
MaxwellToVK : : SurfaceFormat ( runtime - > device , FormatType : : Optimal , true , info . format )
. format ;
image_ci . usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT ;
vk : : Image temp_image = runtime - > memory_allocator . CreateImage ( image_ci ) ;
scheduler - > RequestOutsideRenderPassOperationContext ( ) ;
auto vk_copies = TransformBufferImageCopies ( copies , offset , temp_wrapper - > aspect_mask ) ;
auto vk_copies = TransformBufferImageCopies ( copies , offset , aspect_mask ) ;
const VkBuffer src_buffer = buffer ;
const VkImage temp_vk_image = * temp_wrapper - > original_ image ;
const VkImageAspectFlags vk_aspect_mask = temp_wrapper - > aspect_mask ;
const VkImage temp_vk_image = * temp_image ;
const VkImageAspectFlags vk_aspect_mask = aspect_mask ;
scheduler - > Record ( [ src_buffer , temp_vk_image , vk_aspect_mask , vk_copies ,
keep = temp_wrapper ] ( vk : : CommandBuffer cmdbuf ) {
scheduler - > Record ( [ src_buffer , temp_vk_image , vk_aspect_mask ,
vk_copies ] ( vk : : CommandBuffer cmdbuf ) {
CopyBufferToImage ( cmdbuf , src_buffer , temp_vk_image , vk_aspect_mask , false , VideoCommon : : FixSmallVectorADL ( vk_copies ) ) ;
} ) ;
// Use MSAACopyPass to convert from non-MSAA to MSAA
const auto [ samples_x , samples_y ] = VideoCommon : : SamplesLog2 ( info . num_samples ) ;
std : : vector < VideoCommon : : ImageCopy > image_copies ;
image_copies . reserve ( copies . size ( ) ) ;
for ( const auto & copy : copies ) {
VideoCommon : : ImageCopy image_copy { } ;
image_copy . src_offset = { 0 , 0 , 0 } ; // Use zero offset for source
image_copy . dst_offset = copy . image_offset ;
image_copy . src_offset = { 0 , 0 , 0 } ;
image_copy . dst_offset = { copy . image_offset . x > > samples_x ,
copy . image_offset . y > > samples_y , copy . image_offset . z } ;
image_copy . src_subresource = copy . image_subresource ;
image_copy . dst_subresource = copy . image_subresource ;
image_copy . extent = copy . image_extent ;
image_copy . extent = { copy . image_extent . width > > samples_x ,
copy . image_extent . height > > samples_y , copy . image_extent . depth } ;
image_copies . push_back ( image_copy ) ;
}
runtime - > msaa_copy_pass - > CopyImage ( * this , * temp_wrapper , image_copies ,
/*msaa_to_non_msaa=*/ false ) ;
std : : exchange ( initialized , true ) ;
runtime - > blit_image_helper . CopyMSAA ( runtime - > render_pass_cache , Handle ( ) , info . format ,
temp_vk_image , info . format , info . num_samples ,
image_copies , false ) ;
initialized = true ;
runtime - > pending_msaa_images . emplace_back ( scheduler - > CurrentTick ( ) , std : : move ( temp_image ) ) ;
const u64 tick = scheduler - > Flush ( ) ;
scheduler - > Wait ( tick ) ;
if ( is_rescaled ) {
ScaleUp ( ) ;
}
return ;
}
if ( info . num_samples > 1 ) {
LOG_WARNING ( Render_Vulkan , " MSAA upload not implemented for format {} " , info . format ) ;
if ( is_rescaled ) {
ScaleUp ( ) ;
}
return ;
}
// Regular non-MSAA upload (original behavior preserved)
scheduler - > RequestOutsideRenderPassOperationContext ( ) ;
auto vk_copies = TransformBufferImageCopies ( copies , offset , aspect_mask ) ;
const VkBuffer src_buffer = buffer ;
@ -1794,22 +1992,45 @@ void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<size_t> o
ScaleDown ( ) ;
}
// RE-USE MSAA UPLOAD CODE BUT NOW FOR DOWNLOAD
if ( info . num_samples > 1 & & runtime - > msaa_copy_pass ) {
// TODO: Depth/stencil formats need special handling
if ( aspect_mask = = VK_IMAGE_ASPECT_COLOR_BIT ) {
if ( info . num_samples > 1 ) {
if ( aspect_mask = = VK_IMAGE_ASPECT_COLOR_BIT & &
! VideoCore : : Surface : : IsPixelFormatInteger ( info . format ) ) {
ImageInfo temp_info = info ;
temp_info . num_samples = 1 ;
VkImageCreateInfo image_ci = MakeImageCreateInfo ( runtime - > device , temp_info ) ;
image_ci . usage = original_image . UsageFlags ( ) ;
image_ci . format =
MaxwellToVK : : SurfaceFormat ( runtime - > device , FormatType : : Optimal , true , info . format )
. format ;
image_ci . usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT ;
vk : : Image temp_image = runtime - > memory_allocator . CreateImage ( image_ci ) ;
const VkImage temp_vk_image = * temp_image ;
Image temp_wrapper ( * runtime , temp_info , 0 , 0 ) ;
temp_wrapper . original_image = std : : move ( temp_image ) ;
temp_wrapper . current_image = & Image : : original_image ;
temp_wrapper . aspect_mask = aspect_mask ;
temp_wrapper . initialized = true ;
scheduler - > RequestOutsideRenderPassOperationContext ( ) ;
scheduler - > Record ( [ temp_vk_image ] ( vk : : CommandBuffer cmdbuf ) {
const VkImageMemoryBarrier init_barrier {
. sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER ,
. pNext = nullptr ,
. srcAccessMask = 0 ,
. dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT ,
. oldLayout = VK_IMAGE_LAYOUT_UNDEFINED ,
. newLayout = VK_IMAGE_LAYOUT_GENERAL ,
. srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. image = temp_vk_image ,
. subresourceRange {
. aspectMask = VK_IMAGE_ASPECT_COLOR_BIT ,
. baseMipLevel = 0 ,
. levelCount = VK_REMAINING_MIP_LEVELS ,
. baseArrayLayer = 0 ,
. layerCount = VK_REMAINING_ARRAY_LAYERS ,
} ,
} ;
cmdbuf . PipelineBarrier ( VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT ,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT , 0 ,
init_barrier ) ;
} ) ;
std : : vector < VideoCommon : : ImageCopy > image_copies ;
for ( const auto & copy : copies ) {
@ -1822,7 +2043,9 @@ void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<size_t> o
image_copies . push_back ( image_copy ) ;
}
runtime - > msaa_copy_pass - > CopyImage ( temp_wrapper , * this , image_copies , true ) ;
runtime - > blit_image_helper . CopyMSAA ( runtime - > render_pass_cache , temp_vk_image ,
info . format , Handle ( ) , info . format ,
info . num_samples , image_copies , true ) ;
boost : : container : : small_vector < VkBuffer , 8 > buffers_vector { } ;
boost : : container : : small_vector < boost : : container : : small_vector < VkBufferImageCopy , 16 > , 8 >
@ -1834,7 +2057,7 @@ void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<size_t> o
}
scheduler - > RequestOutsideRenderPassOperationContext ( ) ;
scheduler - > Record ( [ buffers = std : : move ( buffers_vector ) , image = * temp_wrapper . original _image,
scheduler - > Record ( [ buffers = std : : move ( buffers_vector ) , image = temp_vk _image,
aspect_mask_ = aspect_mask , vk_copies ] ( vk : : CommandBuffer cmdbuf ) {
const VkImageMemoryBarrier read_barrier {
. sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER ,
@ -1889,6 +2112,8 @@ void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<size_t> o
cmdbuf . PipelineBarrier ( VK_PIPELINE_STAGE_TRANSFER_BIT , vk : : PIPELINE_STAGE_GRAPHICS_COMPUTE ,
0 , memory_write_barrier , nullptr , image_write_barrier ) ;
} ) ;
runtime - > pending_msaa_images . emplace_back ( scheduler - > CurrentTick ( ) ,
std : : move ( temp_image ) ) ;
return ;
}
} else {
@ -2074,10 +2299,11 @@ bool Image::BlitScaleHelper(bool scale_up) {
blit_view = std : : make_unique < ImageView > ( * runtime , view_info , NULL_IMAGE_ID , * this ) ;
}
const u32 src_width = scale_up ? info . size . width : scaled_width ;
const u32 src_height = scale_up ? info . size . height : scaled_height ;
const u32 dst_width = scale_up ? scaled_width : info . size . width ;
const u32 dst_height = scale_up ? scaled_height : info . size . height ;
const auto [ samples_x , samples_y ] = VideoCommon : : SamplesLog2 ( info . num_samples ) ;
const u32 src_width = ( scale_up ? info . size . width : scaled_width ) > > samples_x ;
const u32 src_height = ( scale_up ? info . size . height : scaled_height ) > > samples_y ;
const u32 dst_width = ( scale_up ? scaled_width : info . size . width ) > > samples_x ;
const u32 dst_height = ( scale_up ? scaled_height : info . size . height ) > > samples_y ;
const Region2D src_region {
. start = { 0 , 0 } ,
. end = { s32 ( src_width ) , s32 ( src_height ) } ,
@ -2087,17 +2313,23 @@ bool Image::BlitScaleHelper(bool scale_up) {
. end = { s32 ( dst_width ) , s32 ( dst_height ) } ,
} ;
const VkExtent2D extent {
. width = ( std : : max ) ( scaled_width , info . size . width ) ,
. height = ( std : : max ) ( scaled_height , info . size . height ) ,
. width = ( std : : max ) ( scaled_width , info . size . width ) > > samples_x ,
. height = ( std : : max ) ( scaled_height , info . size . height ) > > samples_y ,
} ;
auto * view_ptr = blit_view . get ( ) ;
if ( aspect_mask = = VK_IMAGE_ASPECT_COLOR_BIT ) {
if ( ! blit_framebuffer )
blit_framebuffer . emplace ( * runtime , view_ptr , nullptr , extent , scale_up ) ;
runtime - > blit_image_helper . BlitColor ( & * blit_framebuffer , * blit_view ,
dst_region , src_region , operation , BLIT_OPERATION ) ;
} else if ( aspect_mask = = ( VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT ) ) {
if ( info . num_samples > 1 ) {
runtime - > blit_image_helper . BlitColorMSAA ( & * blit_framebuffer , * blit_view ,
dst_region , src_region ) ;
} else {
runtime - > blit_image_helper . BlitColor ( & * blit_framebuffer , * blit_view ,
dst_region , src_region , operation , BLIT_OPERATION ) ;
}
} else if ( aspect_mask = = ( VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT ) & &
info . num_samples = = 1 ) {
if ( ! blit_framebuffer )
blit_framebuffer . emplace ( * runtime , nullptr , view_ptr , extent , scale_up ) ;
runtime - > blit_image_helper . BlitDepthStencil ( & * blit_framebuffer , * blit_view ,
@ -2113,7 +2345,8 @@ bool Image::BlitScaleHelper(bool scale_up) {
bool Image : : NeedsScaleHelper ( ) const {
const auto & device = runtime - > device ;
const bool needs_msaa_helper = info . num_samples > 1 & & device . CantBlitMSAA ( ) ;
const bool needs_msaa_helper = info . num_samples > 1 & &
( device . CantBlitMSAA ( ) | | aspect_mask = = VK_IMAGE_ASPECT_COLOR_BIT ) ;
if ( needs_msaa_helper ) {
return true ;
}
@ -2515,11 +2748,76 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
renderpass_key . depth_format = PixelFormat : : Invalid ;
}
renderpass_key . samples = samples ;
const bool do_resolve_color =
samples ! = VK_SAMPLE_COUNT_1_BIT & & num_colors > 0 & & runtime . device . IsTiler ( ) ;
renderpass_key . resolve_color = do_resolve_color ;
discard_msaa_color =
ENABLE_MSAA_RESOLVE_CONSUME & & ENABLE_MSAA_COLOR_DISCARD & & do_resolve_color ;
renderpass = runtime . render_pass_cache . Get ( renderpass_key ) ;
render_pass_key = renderpass_key ;
render_pass_cache = & runtime . render_pass_cache ;
render_area . width = ( std : : min ) ( render_area . width , width ) ;
render_area . height = ( std : : min ) ( render_area . height , height ) ;
if ( do_resolve_color ) {
const u32 layers = static_cast < u32 > ( ( std : : max ) ( num_layers , 1 ) ) ;
for ( size_t index = 0 ; index < NUM_RT ; + + index ) {
const PixelFormat format = renderpass_key . color_formats [ index ] ;
if ( format = = PixelFormat : : Invalid ) {
continue ;
}
const VkFormat vk_format =
MaxwellToVK : : SurfaceFormat ( runtime . device , FormatType : : Optimal , true , format ) . format ;
if ( ENABLE_MSAA_RESOLVE_CONSUME ) {
const VkImage msaa_image = images [ rt_map [ index ] ] ;
attachments . push_back ( runtime . GetOrCreateResolveShadow ( msaa_image , vk_format ,
render_area , layers ) ) ;
continue ;
}
VkImageCreateInfo resolve_ci {
. sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO ,
. pNext = nullptr ,
. flags = 0 ,
. imageType = VK_IMAGE_TYPE_2D ,
. format = vk_format ,
. extent = { render_area . width , render_area . height , 1 } ,
. mipLevels = 1 ,
. arrayLayers = layers ,
. samples = VK_SAMPLE_COUNT_1_BIT ,
. tiling = VK_IMAGE_TILING_OPTIMAL ,
. usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT ,
. sharingMode = VK_SHARING_MODE_EXCLUSIVE ,
. queueFamilyIndexCount = 0 ,
. pQueueFamilyIndices = nullptr ,
. initialLayout = VK_IMAGE_LAYOUT_UNDEFINED ,
} ;
vk : : Image resolve_image = runtime . memory_allocator . CreateImage ( resolve_ci ) ;
vk : : ImageView resolve_view =
runtime . device . GetLogical ( ) . CreateImageView ( VkImageViewCreateInfo {
. sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO ,
. pNext = nullptr ,
. flags = 0 ,
. image = * resolve_image ,
. viewType = layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D ,
. format = vk_format ,
. components { } ,
. subresourceRange {
. aspectMask = VK_IMAGE_ASPECT_COLOR_BIT ,
. baseMipLevel = 0 ,
. levelCount = 1 ,
. baseArrayLayer = 0 ,
. layerCount = layers ,
} ,
} ) ;
attachments . push_back ( * resolve_view ) ;
resolve_images . push_back ( std : : move ( resolve_image ) ) ;
resolve_image_views . push_back ( std : : move ( resolve_view ) ) ;
}
}
num_color_buffers = static_cast < u32 > ( num_colors ) ;
framebuffer = runtime . device . GetLogical ( ) . CreateFramebuffer ( {
. sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO ,
@ -2534,6 +2832,18 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
} ) ;
}
VkRenderPass Framebuffer : : RenderPassVariant ( u32 color_clear_mask , bool depth_stencil_clear ,
u32 color_discard_mask ) const {
if ( color_clear_mask = = 0 & & ! depth_stencil_clear & & color_discard_mask = = 0 ) {
return renderpass ;
}
RenderPassKey key = render_pass_key ;
key . color_clear_mask = color_clear_mask ;
key . depth_stencil_clear = depth_stencil_clear ;
key . color_discard_mask = color_discard_mask ;
return render_pass_cache - > Get ( key ) ;
}
void TextureCacheRuntime : : AccelerateImageUpload (
Image & image , const StagingBufferRef & map ,
std : : span < const VideoCommon : : SwizzleParameters > swizzles ,