// Copyright 2019 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include #include "video_core/engines/fermi_2d.h" #include "video_core/renderer_vulkan/blit_image.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { using Tegra::Engines::Fermi2D; using Tegra::Texture::SwizzleSource; using Tegra::Texture::TextureMipmapFilter; using VideoCommon::BufferImageCopy; using VideoCommon::ImageInfo; using VideoCommon::ImageType; using VideoCommon::SubresourceRange; using VideoCore::Surface::IsPixelFormatASTC; namespace { constexpr std::array ATTACHMENT_REFERENCES{ VkAttachmentReference{0, VK_IMAGE_LAYOUT_GENERAL}, VkAttachmentReference{1, VK_IMAGE_LAYOUT_GENERAL}, VkAttachmentReference{2, VK_IMAGE_LAYOUT_GENERAL}, VkAttachmentReference{3, VK_IMAGE_LAYOUT_GENERAL}, VkAttachmentReference{4, VK_IMAGE_LAYOUT_GENERAL}, VkAttachmentReference{5, VK_IMAGE_LAYOUT_GENERAL}, VkAttachmentReference{6, VK_IMAGE_LAYOUT_GENERAL}, VkAttachmentReference{7, VK_IMAGE_LAYOUT_GENERAL}, VkAttachmentReference{8, VK_IMAGE_LAYOUT_GENERAL}, }; constexpr VkBorderColor ConvertBorderColor(const std::array& color) { if (color == std::array{0, 0, 0, 0}) { return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; } else if (color == std::array{0, 0, 0, 1}) { return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; } else if (color == std::array{1, 1, 1, 1}) { return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; } if (color[0] + color[1] + color[2] > 1.35f) { // If color elements are brighter than roughly 0.5 average, use white border return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; } else if (color[3] > 0.5f) { return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; } else { return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; } } [[nodiscard]] VkImageType ConvertImageType(const ImageType type) { switch (type) { case ImageType::e1D: return VK_IMAGE_TYPE_1D; case ImageType::e2D: case ImageType::Linear: return VK_IMAGE_TYPE_2D; case ImageType::e3D: return VK_IMAGE_TYPE_3D; case ImageType::Buffer: break; } UNREACHABLE_MSG("Invalid image type={}", type); return {}; } [[nodiscard]] VkSampleCountFlagBits ConvertSampleCount(u32 num_samples) { switch (num_samples) { case 1: return VK_SAMPLE_COUNT_1_BIT; case 2: return VK_SAMPLE_COUNT_2_BIT; case 4: return VK_SAMPLE_COUNT_4_BIT; case 8: return VK_SAMPLE_COUNT_8_BIT; case 16: return VK_SAMPLE_COUNT_16_BIT; default: UNREACHABLE_MSG("Invalid number of samples={}", num_samples); return VK_SAMPLE_COUNT_1_BIT; } } [[nodiscard]] VkImageUsageFlags ImageUsageFlags(const MaxwellToVK::FormatInfo& info, PixelFormat format) { VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; if (info.attachable) { switch (VideoCore::Surface::GetFormatType(format)) { case VideoCore::Surface::SurfaceType::ColorTexture: usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; break; case VideoCore::Surface::SurfaceType::Depth: case VideoCore::Surface::SurfaceType::DepthStencil: usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; break; default: UNREACHABLE_MSG("Invalid surface type"); } } if (info.storage) { usage |= VK_IMAGE_USAGE_STORAGE_BIT; } return usage; } /// Returns the preferred format for a VkImage [[nodiscard]] PixelFormat StorageFormat(PixelFormat format) { switch (format) { case PixelFormat::A8B8G8R8_SRGB: return PixelFormat::A8B8G8R8_UNORM; default: return format; } } [[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) { const PixelFormat format = StorageFormat(info.format); const auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, format); VkImageCreateFlags flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; if (info.type == ImageType::e2D && info.resources.layers >= 6 && info.size.width == info.size.height) { flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; } if (info.type == ImageType::e3D) { flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; } const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(info.num_samples); return VkImageCreateInfo{ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = nullptr, .flags = flags, .imageType = ConvertImageType(info.type), .format = format_info.format, .extent{ .width = info.size.width >> samples_x, .height = info.size.height >> samples_y, .depth = info.size.depth, }, .mipLevels = static_cast(info.resources.levels), .arrayLayers = static_cast(info.resources.layers), .samples = ConvertSampleCount(info.num_samples), .tiling = VK_IMAGE_TILING_OPTIMAL, .usage = ImageUsageFlags(format_info, format), .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, }; } [[nodiscard]] vk::Image MakeImage(const Device& device, const ImageInfo& info) { if (info.type == ImageType::Buffer) { return vk::Image{}; } return device.GetLogical().CreateImage(MakeImageCreateInfo(device, info)); } [[nodiscard]] vk::Buffer MakeBuffer(const Device& device, const ImageInfo& info) { if (info.type != ImageType::Buffer) { return vk::Buffer{}; } const size_t bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format); return device.GetLogical().CreateBuffer(VkBufferCreateInfo{ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, .flags = 0, .size = info.size.width * bytes_per_block, .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, }); } [[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) { switch (VideoCore::Surface::GetFormatType(format)) { case VideoCore::Surface::SurfaceType::ColorTexture: return VK_IMAGE_ASPECT_COLOR_BIT; case VideoCore::Surface::SurfaceType::Depth: return VK_IMAGE_ASPECT_DEPTH_BIT; case VideoCore::Surface::SurfaceType::DepthStencil: return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; default: UNREACHABLE_MSG("Invalid surface type"); return VkImageAspectFlags{}; } } [[nodiscard]] VkImageAspectFlags ImageViewAspectMask(const VideoCommon::ImageViewInfo& info) { if (info.IsRenderTarget()) { return ImageAspectMask(info.format); } const bool is_first = info.Swizzle()[0] == SwizzleSource::R; switch (info.format) { case PixelFormat::D24_UNORM_S8_UINT: case PixelFormat::D32_FLOAT_S8_UINT: return is_first ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_STENCIL_BIT; case PixelFormat::S8_UINT_D24_UNORM: return is_first ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; case PixelFormat::D16_UNORM: case PixelFormat::D32_FLOAT: return VK_IMAGE_ASPECT_DEPTH_BIT; default: return VK_IMAGE_ASPECT_COLOR_BIT; } } [[nodiscard]] VkAttachmentDescription AttachmentDescription(const Device& device, const ImageView* image_view) { using MaxwellToVK::SurfaceFormat; const PixelFormat pixel_format = image_view->format; return VkAttachmentDescription{ .flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT, .format = SurfaceFormat(device, FormatType::Optimal, true, pixel_format).format, .samples = image_view->Samples(), .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE, .initialLayout = VK_IMAGE_LAYOUT_GENERAL, .finalLayout = VK_IMAGE_LAYOUT_GENERAL, }; } [[nodiscard]] VkComponentSwizzle ComponentSwizzle(SwizzleSource swizzle) { switch (swizzle) { case SwizzleSource::Zero: return VK_COMPONENT_SWIZZLE_ZERO; case SwizzleSource::R: return VK_COMPONENT_SWIZZLE_R; case SwizzleSource::G: return VK_COMPONENT_SWIZZLE_G; case SwizzleSource::B: return VK_COMPONENT_SWIZZLE_B; case SwizzleSource::A: return VK_COMPONENT_SWIZZLE_A; case SwizzleSource::OneFloat: case SwizzleSource::OneInt: return VK_COMPONENT_SWIZZLE_ONE; } UNREACHABLE_MSG("Invalid swizzle={}", swizzle); return VK_COMPONENT_SWIZZLE_ZERO; } [[nodiscard]] VkImageViewType ImageViewType(VideoCommon::ImageViewType type) { switch (type) { case VideoCommon::ImageViewType::e1D: return VK_IMAGE_VIEW_TYPE_1D; case VideoCommon::ImageViewType::e2D: return VK_IMAGE_VIEW_TYPE_2D; case VideoCommon::ImageViewType::Cube: return VK_IMAGE_VIEW_TYPE_CUBE; case VideoCommon::ImageViewType::e3D: return VK_IMAGE_VIEW_TYPE_3D; case VideoCommon::ImageViewType::e1DArray: return VK_IMAGE_VIEW_TYPE_1D_ARRAY; case VideoCommon::ImageViewType::e2DArray: return VK_IMAGE_VIEW_TYPE_2D_ARRAY; case VideoCommon::ImageViewType::CubeArray: return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; case VideoCommon::ImageViewType::Rect: LOG_WARNING(Render_Vulkan, "Unnormalized image view type not supported"); return VK_IMAGE_VIEW_TYPE_2D; case VideoCommon::ImageViewType::Buffer: UNREACHABLE_MSG("Texture buffers can't be image views"); return VK_IMAGE_VIEW_TYPE_1D; } UNREACHABLE_MSG("Invalid image view type={}", type); return VK_IMAGE_VIEW_TYPE_2D; } [[nodiscard]] VkImageSubresourceLayers MakeImageSubresourceLayers( VideoCommon::SubresourceLayers subresource, VkImageAspectFlags aspect_mask) { return VkImageSubresourceLayers{ .aspectMask = aspect_mask, .mipLevel = static_cast(subresource.base_level), .baseArrayLayer = static_cast(subresource.base_layer), .layerCount = static_cast(subresource.num_layers), }; } [[nodiscard]] VkOffset3D MakeOffset3D(VideoCommon::Offset3D offset3d) { return VkOffset3D{ .x = offset3d.x, .y = offset3d.y, .z = offset3d.z, }; } [[nodiscard]] VkExtent3D MakeExtent3D(VideoCommon::Extent3D extent3d) { return VkExtent3D{ .width = static_cast(extent3d.width), .height = static_cast(extent3d.height), .depth = static_cast(extent3d.depth), }; } [[nodiscard]] VkImageCopy MakeImageCopy(const VideoCommon::ImageCopy& copy, VkImageAspectFlags aspect_mask) noexcept { return VkImageCopy{ .srcSubresource = MakeImageSubresourceLayers(copy.src_subresource, aspect_mask), .srcOffset = MakeOffset3D(copy.src_offset), .dstSubresource = MakeImageSubresourceLayers(copy.dst_subresource, aspect_mask), .dstOffset = MakeOffset3D(copy.dst_offset), .extent = MakeExtent3D(copy.extent), }; } [[nodiscard]] std::vector TransformBufferCopies( std::span copies, size_t buffer_offset) { std::vector result(copies.size()); std::ranges::transform( copies, result.begin(), [buffer_offset](const VideoCommon::BufferCopy& copy) { return VkBufferCopy{ .srcOffset = static_cast(copy.src_offset + buffer_offset), .dstOffset = static_cast(copy.dst_offset), .size = static_cast(copy.size), }; }); return result; } [[nodiscard]] std::vector TransformBufferImageCopies( std::span copies, size_t buffer_offset, VkImageAspectFlags aspect_mask) { struct Maker { VkBufferImageCopy operator()(const BufferImageCopy& copy) const { return VkBufferImageCopy{ .bufferOffset = copy.buffer_offset + buffer_offset, .bufferRowLength = copy.buffer_row_length, .bufferImageHeight = copy.buffer_image_height, .imageSubresource = { .aspectMask = aspect_mask, .mipLevel = static_cast(copy.image_subresource.base_level), .baseArrayLayer = static_cast(copy.image_subresource.base_layer), .layerCount = static_cast(copy.image_subresource.num_layers), }, .imageOffset = { .x = copy.image_offset.x, .y = copy.image_offset.y, .z = copy.image_offset.z, }, .imageExtent = { .width = copy.image_extent.width, .height = copy.image_extent.height, .depth = copy.image_extent.depth, }, }; } size_t buffer_offset; VkImageAspectFlags aspect_mask; }; if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { std::vector result(copies.size() * 2); std::ranges::transform(copies, result.begin(), Maker{buffer_offset, VK_IMAGE_ASPECT_DEPTH_BIT}); std::ranges::transform(copies, result.begin() + copies.size(), Maker{buffer_offset, VK_IMAGE_ASPECT_STENCIL_BIT}); return result; } else { std::vector result(copies.size()); std::ranges::transform(copies, result.begin(), Maker{buffer_offset, aspect_mask}); return result; } } [[nodiscard]] VkImageSubresourceRange MakeSubresourceRange(VkImageAspectFlags aspect_mask, const SubresourceRange& range) { return VkImageSubresourceRange{ .aspectMask = aspect_mask, .baseMipLevel = static_cast(range.base.level), .levelCount = static_cast(range.extent.levels), .baseArrayLayer = static_cast(range.base.layer), .layerCount = static_cast(range.extent.layers), }; } [[nodiscard]] VkImageSubresourceRange MakeSubresourceRange(const ImageView* image_view) { SubresourceRange range = image_view->range; if (True(image_view->flags & VideoCommon::ImageViewFlagBits::Slice)) { // Slice image views always affect a single layer, but their subresource range corresponds // to the slice. Override the value to affect a single layer. range.base.layer = 0; range.extent.layers = 1; } return MakeSubresourceRange(ImageAspectMask(image_view->format), range); } [[nodiscard]] VkImageSubresourceLayers MakeSubresourceLayers(const ImageView* image_view) { return VkImageSubresourceLayers{ .aspectMask = ImageAspectMask(image_view->format), .mipLevel = static_cast(image_view->range.base.level), .baseArrayLayer = static_cast(image_view->range.base.layer), .layerCount = static_cast(image_view->range.extent.layers), }; } [[nodiscard]] constexpr SwizzleSource ConvertGreenRed(SwizzleSource value) { switch (value) { case SwizzleSource::G: return SwizzleSource::R; default: return value; } } void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage image, VkImageAspectFlags aspect_mask, bool is_initialized, std::span copies) { static constexpr VkAccessFlags WRITE_ACCESS_FLAGS = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; static constexpr VkAccessFlags READ_ACCESS_FLAGS = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; const VkImageMemoryBarrier read_barrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = WRITE_ACCESS_FLAGS, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = image, .subresourceRange{ .aspectMask = aspect_mask, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; const VkImageMemoryBarrier write_barrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = WRITE_ACCESS_FLAGS | READ_ACCESS_FLAGS, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = image, .subresourceRange{ .aspectMask = aspect_mask, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, read_barrier); cmdbuf.CopyBufferToImage(src_buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copies); // TODO: Move this to another API cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, write_barrier); } [[nodiscard]] VkImageBlit MakeImageBlit(const std::array& dst_region, const std::array& src_region, const VkImageSubresourceLayers& dst_layers, const VkImageSubresourceLayers& src_layers) { return VkImageBlit{ .srcSubresource = src_layers, .srcOffsets = { { .x = src_region[0].x, .y = src_region[0].y, .z = 0, }, { .x = src_region[1].x, .y = src_region[1].y, .z = 1, }, }, .dstSubresource = dst_layers, .dstOffsets = { { .x = dst_region[0].x, .y = dst_region[0].y, .z = 0, }, { .x = dst_region[1].x, .y = dst_region[1].y, .z = 1, }, }, }; } [[nodiscard]] VkImageResolve MakeImageResolve(const std::array& dst_region, const std::array& src_region, const VkImageSubresourceLayers& dst_layers, const VkImageSubresourceLayers& src_layers) { return VkImageResolve{ .srcSubresource = src_layers, .srcOffset = { .x = src_region[0].x, .y = src_region[0].y, .z = 0, }, .dstSubresource = dst_layers, .dstOffset = { .x = dst_region[0].x, .y = dst_region[0].y, .z = 0, }, .extent = { .width = static_cast(dst_region[1].x - dst_region[0].x), .height = static_cast(dst_region[1].y - dst_region[0].y), .depth = 1, }, }; } struct RangedBarrierRange { u32 min_mip = std::numeric_limits::max(); u32 max_mip = std::numeric_limits::min(); u32 min_layer = std::numeric_limits::max(); u32 max_layer = std::numeric_limits::min(); void AddLayers(const VkImageSubresourceLayers& layers) { min_mip = std::min(min_mip, layers.mipLevel); max_mip = std::max(max_mip, layers.mipLevel + 1); min_layer = std::min(min_layer, layers.baseArrayLayer); max_layer = std::max(max_layer, layers.baseArrayLayer + layers.layerCount); } VkImageSubresourceRange SubresourceRange(VkImageAspectFlags aspect_mask) const noexcept { return VkImageSubresourceRange{ .aspectMask = aspect_mask, .baseMipLevel = min_mip, .levelCount = max_mip - min_mip, .baseArrayLayer = min_layer, .layerCount = max_layer - min_layer, }; } }; } // Anonymous namespace void TextureCacheRuntime::Finish() { scheduler.Finish(); } StagingBufferRef TextureCacheRuntime::UploadStagingBuffer(size_t size) { return staging_buffer_pool.Request(size, MemoryUsage::Upload); } StagingBufferRef TextureCacheRuntime::DownloadStagingBuffer(size_t size) { return staging_buffer_pool.Request(size, MemoryUsage::Download); } void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src, const std::array& dst_region, const std::array& src_region, Tegra::Engines::Fermi2D::Filter filter, Tegra::Engines::Fermi2D::Operation operation) { const VkImageAspectFlags aspect_mask = ImageAspectMask(src.format); const bool is_dst_msaa = dst.Samples() != VK_SAMPLE_COUNT_1_BIT; const bool is_src_msaa = src.Samples() != VK_SAMPLE_COUNT_1_BIT; ASSERT(aspect_mask == ImageAspectMask(dst.format)); if (aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT && !is_src_msaa && !is_dst_msaa) { blit_image_helper.BlitColor(dst_framebuffer, src, dst_region, src_region, filter, operation); return; } if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { if (!device.IsBlitDepthStencilSupported()) { UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa); blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(), dst_region, src_region, filter, operation); return; } } ASSERT(src.ImageFormat() == dst.ImageFormat()); 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_resolve = is_src_msaa && !is_dst_msaa; scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([filter, dst_region, src_region, dst_image, src_image, dst_layers, src_layers, aspect_mask, is_resolve](vk::CommandBuffer cmdbuf) { const std::array read_barriers{ VkImageMemoryBarrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = src_image, .subresourceRange{ .aspectMask = aspect_mask, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }, VkImageMemoryBarrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_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{ .aspectMask = aspect_mask, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }, }; VkImageMemoryBarrier write_barrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_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{ .aspectMask = aspect_mask, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, nullptr, nullptr, read_barriers); if (is_resolve) { cmdbuf.ResolveImage(src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, MakeImageResolve(dst_region, src_region, dst_layers, src_layers)); } else { const bool is_linear = filter == Fermi2D::Filter::Bilinear; const VkFilter vk_filter = is_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; cmdbuf.BlitImage( src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, MakeImageBlit(dst_region, src_region, dst_layers, src_layers), vk_filter); } cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, write_barrier); }); } void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view) { switch (dst_view.format) { case PixelFormat::R16_UNORM: if (src_view.format == PixelFormat::D16_UNORM) { return blit_image_helper.ConvertD16ToR16(dst, src_view); } break; case PixelFormat::R32_FLOAT: if (src_view.format == PixelFormat::D32_FLOAT) { return blit_image_helper.ConvertD32ToR32(dst, src_view); } break; case PixelFormat::D16_UNORM: if (src_view.format == PixelFormat::R16_UNORM) { return blit_image_helper.ConvertR16ToD16(dst, src_view); } break; case PixelFormat::D32_FLOAT: if (src_view.format == PixelFormat::R32_FLOAT) { return blit_image_helper.ConvertR32ToD32(dst, src_view); } break; default: break; } UNIMPLEMENTED_MSG("Unimplemented format copy from {} to {}", src_view.format, dst_view.format); } void TextureCacheRuntime::CopyImage(Image& dst, Image& src, std::span copies) { std::vector vk_copies(copies.size()); const VkImageAspectFlags aspect_mask = dst.AspectMask(); ASSERT(aspect_mask == src.AspectMask()); std::ranges::transform(copies, vk_copies.begin(), [aspect_mask](const auto& copy) { return MakeImageCopy(copy, aspect_mask); }); const VkImage dst_image = dst.Handle(); const VkImage src_image = src.Handle(); scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([dst_image, src_image, aspect_mask, vk_copies](vk::CommandBuffer cmdbuf) { RangedBarrierRange dst_range; RangedBarrierRange src_range; for (const VkImageCopy& copy : vk_copies) { dst_range.AddLayers(copy.dstSubresource); src_range.AddLayers(copy.srcSubresource); } const std::array read_barriers{ VkImageMemoryBarrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = src_image, .subresourceRange = src_range.SubresourceRange(aspect_mask), }, VkImageMemoryBarrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_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 = dst_range.SubresourceRange(aspect_mask), }, }; const VkImageMemoryBarrier write_barrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_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 = dst_range.SubresourceRange(aspect_mask), }; cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, {}, read_barriers); cmdbuf.CopyImage(src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, vk_copies); cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, write_barrier); }); } Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_) : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime.scheduler}, image(MakeImage(runtime.device, info)), buffer(MakeBuffer(runtime.device, info)), aspect_mask(ImageAspectMask(info.format)) { if (image) { commit = runtime.memory_allocator.Commit(image, MemoryUsage::DeviceLocal); } else { commit = runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal); } if (IsPixelFormatASTC(info.format) && !runtime.device.IsOptimalAstcSupported()) { flags |= VideoCommon::ImageFlagBits::Converted; } if (runtime.device.HasDebuggingToolAttached()) { if (image) { image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); } else { buffer.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); } } } void Image::UploadMemory(const StagingBufferRef& map, std::span copies) { // TODO: Move this to another API scheduler->RequestOutsideRenderPassOperationContext(); std::vector vk_copies = TransformBufferImageCopies(copies, map.offset, aspect_mask); const VkBuffer src_buffer = map.buffer; const VkImage vk_image = *image; const VkImageAspectFlags vk_aspect_mask = aspect_mask; const bool is_initialized = std::exchange(initialized, true); scheduler->Record([src_buffer, vk_image, vk_aspect_mask, is_initialized, vk_copies](vk::CommandBuffer cmdbuf) { CopyBufferToImage(cmdbuf, src_buffer, vk_image, vk_aspect_mask, is_initialized, vk_copies); }); } void Image::UploadMemory(const StagingBufferRef& map, std::span copies) { // TODO: Move this to another API scheduler->RequestOutsideRenderPassOperationContext(); std::vector vk_copies = TransformBufferCopies(copies, map.offset); const VkBuffer src_buffer = map.buffer; const VkBuffer dst_buffer = *buffer; scheduler->Record([src_buffer, dst_buffer, vk_copies](vk::CommandBuffer cmdbuf) { // TODO: Barriers cmdbuf.CopyBuffer(src_buffer, dst_buffer, vk_copies); }); } void Image::DownloadMemory(const StagingBufferRef& map, std::span copies) { std::vector vk_copies = TransformBufferImageCopies(copies, map.offset, aspect_mask); scheduler->Record([buffer = map.buffer, image = *image, aspect_mask = aspect_mask, vk_copies](vk::CommandBuffer cmdbuf) { const VkImageMemoryBarrier read_barrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_MEMORY_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 = image, .subresourceRange{ .aspectMask = aspect_mask, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; const VkImageMemoryBarrier image_write_barrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = 0, .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = image, .subresourceRange{ .aspectMask = aspect_mask, .baseMipLevel = 0, .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; const VkMemoryBarrier memory_write_barrier{ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, }; cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, read_barrier); cmdbuf.CopyImageToBuffer(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, vk_copies); cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, memory_write_barrier, nullptr, image_write_barrier); }); } ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info, ImageId image_id_, Image& image) : VideoCommon::ImageViewBase{info, image.info, image_id_}, device{&runtime.device}, image_handle{image.Handle()}, image_format{image.info.format}, samples{ConvertSampleCount( image.info.num_samples)} { const VkImageAspectFlags aspect_mask = ImageViewAspectMask(info); std::array swizzle{ SwizzleSource::R, SwizzleSource::G, SwizzleSource::B, SwizzleSource::A, }; if (!info.IsRenderTarget()) { swizzle = info.Swizzle(); if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed); } } const auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); const VkFormat vk_format = format_info.format; const VkImageViewUsageCreateInfo image_view_usage{ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, .pNext = nullptr, .usage = ImageUsageFlags(format_info, format), }; const VkImageViewCreateInfo create_info{ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = &image_view_usage, .flags = 0, .image = image.Handle(), .viewType = VkImageViewType{}, .format = vk_format, .components{ .r = ComponentSwizzle(swizzle[0]), .g = ComponentSwizzle(swizzle[1]), .b = ComponentSwizzle(swizzle[2]), .a = ComponentSwizzle(swizzle[3]), }, .subresourceRange = MakeSubresourceRange(aspect_mask, info.range), }; const auto create = [&](VideoCommon::ImageViewType view_type, std::optional num_layers) { VkImageViewCreateInfo ci{create_info}; ci.viewType = ImageViewType(view_type); if (num_layers) { ci.subresourceRange.layerCount = *num_layers; } vk::ImageView handle = device->GetLogical().CreateImageView(ci); if (device->HasDebuggingToolAttached()) { handle.SetObjectNameEXT(VideoCommon::Name(*this, view_type).c_str()); } image_views[static_cast(view_type)] = std::move(handle); }; switch (info.type) { case VideoCommon::ImageViewType::e1D: case VideoCommon::ImageViewType::e1DArray: create(VideoCommon::ImageViewType::e1D, 1); create(VideoCommon::ImageViewType::e1DArray, std::nullopt); render_target = Handle(VideoCommon::ImageViewType::e1DArray); break; case VideoCommon::ImageViewType::e2D: case VideoCommon::ImageViewType::e2DArray: create(VideoCommon::ImageViewType::e2D, 1); create(VideoCommon::ImageViewType::e2DArray, std::nullopt); render_target = Handle(VideoCommon::ImageViewType::e2DArray); break; case VideoCommon::ImageViewType::e3D: create(VideoCommon::ImageViewType::e3D, std::nullopt); render_target = Handle(VideoCommon::ImageViewType::e3D); break; case VideoCommon::ImageViewType::Cube: case VideoCommon::ImageViewType::CubeArray: create(VideoCommon::ImageViewType::Cube, 6); create(VideoCommon::ImageViewType::CubeArray, std::nullopt); break; case VideoCommon::ImageViewType::Rect: UNIMPLEMENTED(); break; case VideoCommon::ImageViewType::Buffer: buffer_view = device->GetLogical().CreateBufferView(VkBufferViewCreateInfo{ .sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO, .pNext = nullptr, .flags = 0, .buffer = image.Buffer(), .format = vk_format, .offset = 0, // TODO: Redesign buffer cache to support this .range = image.guest_size_bytes, }); break; } } ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams& params) : VideoCommon::ImageViewBase{params} {} VkImageView ImageView::DepthView() { if (depth_view) { return *depth_view; } depth_view = MakeDepthStencilView(VK_IMAGE_ASPECT_DEPTH_BIT); return *depth_view; } VkImageView ImageView::StencilView() { if (stencil_view) { return *stencil_view; } stencil_view = MakeDepthStencilView(VK_IMAGE_ASPECT_STENCIL_BIT); return *stencil_view; } vk::ImageView ImageView::MakeDepthStencilView(VkImageAspectFlags aspect_mask) { return device->GetLogical().CreateImageView({ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = nullptr, .flags = 0, .image = image_handle, .viewType = ImageViewType(type), .format = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format).format, .components{ .r = VK_COMPONENT_SWIZZLE_IDENTITY, .g = VK_COMPONENT_SWIZZLE_IDENTITY, .b = VK_COMPONENT_SWIZZLE_IDENTITY, .a = VK_COMPONENT_SWIZZLE_IDENTITY, }, .subresourceRange = MakeSubresourceRange(aspect_mask, range), }); } Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& tsc) { const auto& device = runtime.device; const bool arbitrary_borders = runtime.device.IsExtCustomBorderColorSupported(); const std::array color = tsc.BorderColor(); // C++20 bit_cast VkClearColorValue border_color; std::memcpy(&border_color, &color, sizeof(color)); const VkSamplerCustomBorderColorCreateInfoEXT border_ci{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT, .pNext = nullptr, .customBorderColor = border_color, .format = VK_FORMAT_UNDEFINED, }; const void* pnext = nullptr; if (arbitrary_borders) { pnext = &border_ci; } const VkSamplerReductionModeCreateInfoEXT reduction_ci{ .sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT, .pNext = pnext, .reductionMode = MaxwellToVK::SamplerReduction(tsc.reduction_filter), }; if (runtime.device.IsExtSamplerFilterMinmaxSupported()) { pnext = &reduction_ci; } else if (reduction_ci.reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT) { LOG_WARNING(Render_Vulkan, "VK_EXT_sampler_filter_minmax is required"); } // Some games have samplers with garbage. Sanitize them here. const float max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = pnext, .flags = 0, .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), .mipLodBias = tsc.LodBias(), .anisotropyEnable = static_cast(max_anisotropy > 1.0f ? VK_TRUE : VK_FALSE), .maxAnisotropy = max_anisotropy, .compareEnable = tsc.depth_compare_enabled, .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), .borderColor = arbitrary_borders ? VK_BORDER_COLOR_INT_CUSTOM_EXT : ConvertBorderColor(color), .unnormalizedCoordinates = VK_FALSE, }); } Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span color_buffers, ImageView* depth_buffer, const VideoCommon::RenderTargets& key) { std::vector descriptions; std::vector attachments; RenderPassKey renderpass_key{}; s32 num_layers = 1; for (size_t index = 0; index < NUM_RT; ++index) { const ImageView* const color_buffer = color_buffers[index]; if (!color_buffer) { renderpass_key.color_formats[index] = PixelFormat::Invalid; continue; } descriptions.push_back(AttachmentDescription(runtime.device, color_buffer)); attachments.push_back(color_buffer->RenderTarget()); renderpass_key.color_formats[index] = color_buffer->format; num_layers = std::max(num_layers, color_buffer->range.extent.layers); images[num_images] = color_buffer->ImageHandle(); image_ranges[num_images] = MakeSubresourceRange(color_buffer); samples = color_buffer->Samples(); ++num_images; } const size_t num_colors = attachments.size(); const VkAttachmentReference* depth_attachment = depth_buffer ? &ATTACHMENT_REFERENCES[num_colors] : nullptr; if (depth_buffer) { descriptions.push_back(AttachmentDescription(runtime.device, depth_buffer)); attachments.push_back(depth_buffer->RenderTarget()); renderpass_key.depth_format = depth_buffer->format; num_layers = std::max(num_layers, depth_buffer->range.extent.layers); images[num_images] = depth_buffer->ImageHandle(); image_ranges[num_images] = MakeSubresourceRange(depth_buffer); samples = depth_buffer->Samples(); ++num_images; } else { renderpass_key.depth_format = PixelFormat::Invalid; } renderpass_key.samples = samples; const auto& device = runtime.device.GetLogical(); const auto [cache_pair, is_new] = runtime.renderpass_cache.try_emplace(renderpass_key); if (is_new) { const VkSubpassDescription subpass{ .flags = 0, .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .inputAttachmentCount = 0, .pInputAttachments = nullptr, .colorAttachmentCount = static_cast(num_colors), .pColorAttachments = num_colors != 0 ? ATTACHMENT_REFERENCES.data() : nullptr, .pResolveAttachments = nullptr, .pDepthStencilAttachment = depth_attachment, .preserveAttachmentCount = 0, .pPreserveAttachments = nullptr, }; cache_pair->second = device.CreateRenderPass(VkRenderPassCreateInfo{ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .pNext = nullptr, .flags = 0, .attachmentCount = static_cast(descriptions.size()), .pAttachments = descriptions.data(), .subpassCount = 1, .pSubpasses = &subpass, .dependencyCount = 0, .pDependencies = nullptr, }); } renderpass = *cache_pair->second; render_area = VkExtent2D{ .width = key.size.width, .height = key.size.height, }; num_color_buffers = static_cast(num_colors); framebuffer = device.CreateFramebuffer(VkFramebufferCreateInfo{ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .pNext = nullptr, .flags = 0, .renderPass = renderpass, .attachmentCount = static_cast(attachments.size()), .pAttachments = attachments.data(), .width = key.size.width, .height = key.size.height, .layers = static_cast(std::max(num_layers, 1)), }); if (runtime.device.HasDebuggingToolAttached()) { framebuffer.SetObjectNameEXT(VideoCommon::Name(key).c_str()); } } } // namespace Vulkan