summaryrefslogtreecommitdiff
path: root/Source/Core/VideoBackends/Vulkan/TextureCache.cpp
diff options
context:
space:
mode:
authorMat M <mathew1800@gmail.com>2016-09-30 19:15:50 -0400
committerGitHub <noreply@github.com>2016-09-30 19:15:50 -0400
commit46b9383280a623753686ab6df6c479e784e416ae (patch)
tree9ffb2cbdba6d39a29d6fed9df4db03166755a20d /Source/Core/VideoBackends/Vulkan/TextureCache.cpp
parentf3cdc6433cba253fa4485cd7e27025c31410ed0c (diff)
parenta8194cff3ca8f9b85a779da6b8214b58ba3fa7d0 (diff)
Merge pull request #3935 from stenzek/vulkan-pr
Vulkan Backend
Diffstat (limited to 'Source/Core/VideoBackends/Vulkan/TextureCache.cpp')
-rw-r--r--Source/Core/VideoBackends/Vulkan/TextureCache.cpp740
1 files changed, 740 insertions, 0 deletions
diff --git a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp
new file mode 100644
index 0000000000..65ab7d737c
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp
@@ -0,0 +1,740 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cstring>
+#include <vector>
+
+#include "Common/Assert.h"
+#include "Common/CommonFuncs.h"
+
+#include "VideoBackends/Vulkan/CommandBufferManager.h"
+#include "VideoBackends/Vulkan/FramebufferManager.h"
+#include "VideoBackends/Vulkan/ObjectCache.h"
+#include "VideoBackends/Vulkan/PaletteTextureConverter.h"
+#include "VideoBackends/Vulkan/Renderer.h"
+#include "VideoBackends/Vulkan/StagingTexture2D.h"
+#include "VideoBackends/Vulkan/StateTracker.h"
+#include "VideoBackends/Vulkan/StreamBuffer.h"
+#include "VideoBackends/Vulkan/Texture2D.h"
+#include "VideoBackends/Vulkan/TextureCache.h"
+#include "VideoBackends/Vulkan/TextureEncoder.h"
+#include "VideoBackends/Vulkan/Util.h"
+#include "VideoBackends/Vulkan/VulkanContext.h"
+
+#include "VideoCommon/ImageWrite.h"
+
+namespace Vulkan
+{
+TextureCache::TextureCache()
+{
+}
+
+TextureCache::~TextureCache()
+{
+ if (m_initialize_render_pass != VK_NULL_HANDLE)
+ vkDestroyRenderPass(g_vulkan_context->GetDevice(), m_initialize_render_pass, nullptr);
+ if (m_update_render_pass != VK_NULL_HANDLE)
+ vkDestroyRenderPass(g_vulkan_context->GetDevice(), m_update_render_pass, nullptr);
+}
+
+bool TextureCache::Initialize(StateTracker* state_tracker)
+{
+ m_state_tracker = state_tracker;
+ m_texture_upload_buffer =
+ StreamBuffer::Create(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, INITIAL_TEXTURE_UPLOAD_BUFFER_SIZE,
+ MAXIMUM_TEXTURE_UPLOAD_BUFFER_SIZE);
+ if (!m_texture_upload_buffer)
+ {
+ PanicAlert("Failed to create texture upload buffer");
+ return false;
+ }
+
+ if (!CreateRenderPasses())
+ {
+ PanicAlert("Failed to create copy render pass");
+ return false;
+ }
+
+ m_texture_encoder = std::make_unique<TextureEncoder>();
+ if (!m_texture_encoder->Initialize())
+ {
+ PanicAlert("Failed to initialize texture encoder.");
+ return false;
+ }
+
+ m_palette_texture_converter = std::make_unique<PaletteTextureConverter>();
+ if (!m_palette_texture_converter->Initialize())
+ {
+ PanicAlert("Failed to initialize palette texture converter");
+ return false;
+ }
+
+ if (!CompileShaders())
+ {
+ PanicAlert("Failed to compile one or more shaders");
+ return false;
+ }
+
+ return true;
+}
+
+void TextureCache::ConvertTexture(TCacheEntryBase* base_entry, TCacheEntryBase* base_unconverted,
+ void* palette, TlutFormat format)
+{
+ TCacheEntry* entry = static_cast<TCacheEntry*>(base_entry);
+ TCacheEntry* unconverted = static_cast<TCacheEntry*>(base_unconverted);
+ _assert_(entry->config.rendertarget);
+
+ m_palette_texture_converter->ConvertTexture(
+ m_state_tracker, GetRenderPassForTextureUpdate(entry->GetTexture()), entry->GetFramebuffer(),
+ unconverted->GetTexture(), entry->config.width, entry->config.height, palette, format);
+
+ // Render pass transitions to SHADER_READ_ONLY.
+ entry->GetTexture()->OverrideImageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+}
+
+void TextureCache::CopyEFB(u8* dst, u32 format, u32 native_width, u32 bytes_per_row,
+ u32 num_blocks_y, u32 memory_stride, PEControl::PixelFormat src_format,
+ const EFBRectangle& src_rect, bool is_intensity, bool scale_by_half)
+{
+ // A better way of doing this would be nice.
+ FramebufferManager* framebuffer_mgr =
+ static_cast<FramebufferManager*>(g_framebuffer_manager.get());
+
+ // Flush EFB pokes first, as they're expected to be included.
+ framebuffer_mgr->FlushEFBPokes(m_state_tracker);
+
+ // MSAA case where we need to resolve first.
+ // TODO: Do in one pass.
+ TargetRectangle scaled_src_rect = g_renderer->ConvertEFBRectangle(src_rect);
+ VkRect2D region = {{scaled_src_rect.left, scaled_src_rect.top},
+ {static_cast<u32>(scaled_src_rect.GetWidth()),
+ static_cast<u32>(scaled_src_rect.GetHeight())}};
+ Texture2D* src_texture = (src_format == PEControl::Z24) ?
+ framebuffer_mgr->ResolveEFBDepthTexture(m_state_tracker, region) :
+ framebuffer_mgr->ResolveEFBColorTexture(m_state_tracker, region);
+
+ // End render pass before barrier (since we have no self-dependencies)
+ m_state_tracker->EndRenderPass();
+ m_state_tracker->SetPendingRebind();
+ m_state_tracker->InvalidateDescriptorSets();
+ m_state_tracker->OnReadback();
+
+ // Transition to shader resource before reading.
+ VkImageLayout original_layout = src_texture->GetLayout();
+ src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+ m_texture_encoder->EncodeTextureToRam(m_state_tracker, src_texture->GetView(), dst, format,
+ native_width, bytes_per_row, num_blocks_y, memory_stride,
+ src_format, is_intensity, scale_by_half, src_rect);
+
+ // Transition back to original state
+ src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), original_layout);
+}
+
+TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntryConfig& config)
+{
+ // Determine image usage, we need to flag as an attachment if it can be used as a rendertarget.
+ VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT;
+ if (config.rendertarget)
+ usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+
+ // Allocate texture object
+ std::unique_ptr<Texture2D> texture = Texture2D::Create(
+ config.width, config.height, config.levels, config.layers, TEXTURECACHE_TEXTURE_FORMAT,
+ VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL, usage);
+
+ if (!texture)
+ return nullptr;
+
+ // If this is a render target (for efb copies), allocate a framebuffer
+ VkFramebuffer framebuffer = VK_NULL_HANDLE;
+ if (config.rendertarget)
+ {
+ VkImageView framebuffer_attachments[] = {texture->GetView()};
+ VkFramebufferCreateInfo framebuffer_info = {
+ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ nullptr,
+ 0,
+ m_initialize_render_pass,
+ static_cast<u32>(ArraySize(framebuffer_attachments)),
+ framebuffer_attachments,
+ texture->GetWidth(),
+ texture->GetHeight(),
+ texture->GetLayers()};
+
+ VkResult res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &framebuffer_info, nullptr,
+ &framebuffer);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateFramebuffer failed: ");
+ return nullptr;
+ }
+
+ // Clear render targets before use to prevent reading uninitialized memory.
+ VkClearColorValue clear_value = {{0.0f, 0.0f, 0.0f, 1.0f}};
+ VkImageSubresourceRange clear_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, config.levels, 0,
+ config.layers};
+ texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+ vkCmdClearColorImage(g_command_buffer_mgr->GetCurrentInitCommandBuffer(), texture->GetImage(),
+ texture->GetLayout(), &clear_value, 1, &clear_range);
+ }
+
+ return new TCacheEntry(config, this, std::move(texture), framebuffer);
+}
+
+bool TextureCache::CreateRenderPasses()
+{
+ static constexpr VkAttachmentDescription initialize_attachment = {
+ 0,
+ TEXTURECACHE_TEXTURE_FORMAT,
+ VK_SAMPLE_COUNT_1_BIT,
+ VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ VK_ATTACHMENT_STORE_OP_STORE,
+ VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
+
+ static constexpr VkAttachmentDescription update_attachment = {
+ 0,
+ TEXTURECACHE_TEXTURE_FORMAT,
+ VK_SAMPLE_COUNT_1_BIT,
+ VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ VK_ATTACHMENT_STORE_OP_STORE,
+ VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
+
+ static constexpr VkAttachmentReference color_attachment_reference = {
+ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
+
+ static constexpr VkSubpassDescription subpass_description = {
+ 0, VK_PIPELINE_BIND_POINT_GRAPHICS,
+ 0, nullptr,
+ 1, &color_attachment_reference,
+ nullptr, nullptr,
+ 0, nullptr};
+
+ static constexpr VkSubpassDependency initialize_dependancies[] = {
+ {VK_SUBPASS_EXTERNAL, 0, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ VK_DEPENDENCY_BY_REGION_BIT},
+ {0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ VK_ACCESS_SHADER_READ_BIT, VK_DEPENDENCY_BY_REGION_BIT}};
+
+ static constexpr VkSubpassDependency update_dependancies[] = {
+ {VK_SUBPASS_EXTERNAL, 0, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_SHADER_READ_BIT,
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ VK_DEPENDENCY_BY_REGION_BIT},
+ {0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ VK_ACCESS_SHADER_READ_BIT, VK_DEPENDENCY_BY_REGION_BIT}};
+
+ VkRenderPassCreateInfo initialize_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ nullptr,
+ 0,
+ 1,
+ &initialize_attachment,
+ 1,
+ &subpass_description,
+ static_cast<u32>(ArraySize(initialize_dependancies)),
+ initialize_dependancies};
+
+ VkRenderPassCreateInfo update_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ nullptr,
+ 0,
+ 1,
+ &update_attachment,
+ 1,
+ &subpass_description,
+ static_cast<u32>(ArraySize(update_dependancies)),
+ update_dependancies};
+
+ VkResult res = vkCreateRenderPass(g_vulkan_context->GetDevice(), &initialize_info, nullptr,
+ &m_initialize_render_pass);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateRenderPass (initialize) failed: ");
+ return false;
+ }
+
+ res = vkCreateRenderPass(g_vulkan_context->GetDevice(), &update_info, nullptr,
+ &m_update_render_pass);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateRenderPass (update) failed: ");
+ return false;
+ }
+
+ return true;
+}
+
+VkRenderPass TextureCache::GetRenderPassForTextureUpdate(const Texture2D* texture) const
+{
+ // EFB copies can be re-used as part of the texture pool. If this is the case, we need to insert
+ // a pipeline barrier to ensure that all reads from the texture expecting the old data have
+ // completed before overwriting the texture's contents. New textures will be in TRANSFER_DST
+ // due to the clear after creation.
+
+ // These two render passes are compatible, so even though the framebuffer was created with
+ // the initialize render pass it's still allowed.
+
+ if (texture->GetLayout() == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
+ return m_initialize_render_pass;
+ else
+ return m_update_render_pass;
+}
+
+TextureCache::TCacheEntry::TCacheEntry(const TCacheEntryConfig& config_, TextureCache* parent,
+ std::unique_ptr<Texture2D> texture,
+ VkFramebuffer framebuffer)
+ : TCacheEntryBase(config_), m_parent(parent), m_texture(std::move(texture)),
+ m_framebuffer(framebuffer)
+{
+}
+
+TextureCache::TCacheEntry::~TCacheEntry()
+{
+ // Texture is automatically cleaned up, however, we don't want to leave it bound to the state
+ // tracker.
+ m_parent->m_state_tracker->UnbindTexture(m_texture->GetView());
+
+ if (m_framebuffer != VK_NULL_HANDLE)
+ g_command_buffer_mgr->DeferResourceDestruction(m_framebuffer);
+}
+
+void TextureCache::TCacheEntry::Load(unsigned int width, unsigned int height,
+ unsigned int expanded_width, unsigned int level)
+{
+ // Can't copy data larger than the texture extents.
+ width = std::max(1u, std::min(width, m_texture->GetWidth() >> level));
+ height = std::max(1u, std::min(height, m_texture->GetHeight() >> level));
+
+ // We don't care about the existing contents of the texture, so we set the image layout to
+ // VK_IMAGE_LAYOUT_UNDEFINED here. However, if this texture is being re-used from the texture
+ // pool, it may still be in use. We assume that it's not, as non-efb-copy textures are only
+ // returned to the pool when the frame number is different, furthermore, we're doing this
+ // on the initialize command buffer, so a texture being re-used mid-frame would have undesirable
+ // effects regardless.
+ VkImageMemoryBarrier barrier = {
+ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // VkStructureType sType
+ nullptr, // const void* pNext
+ 0, // VkAccessFlags srcAccessMask
+ VK_ACCESS_TRANSFER_WRITE_BIT, // VkAccessFlags dstAccessMask
+ VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout oldLayout
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, // VkImageLayout newLayout
+ VK_QUEUE_FAMILY_IGNORED, // uint32_t srcQueueFamilyIndex
+ VK_QUEUE_FAMILY_IGNORED, // uint32_t dstQueueFamilyIndex
+ m_texture->GetImage(), // VkImage image
+ {VK_IMAGE_ASPECT_COLOR_BIT, level, 1, 0, 1}, // VkImageSubresourceRange subresourceRange
+ };
+ vkCmdPipelineBarrier(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
+ nullptr, 0, nullptr, 1, &barrier);
+
+ // Does this texture data fit within the streaming buffer?
+ u32 upload_width = width;
+ u32 upload_pitch = upload_width * sizeof(u32);
+ u32 upload_size = upload_pitch * height;
+ u32 upload_alignment = static_cast<u32>(g_vulkan_context->GetBufferImageGranularity());
+ u32 source_pitch = expanded_width * 4;
+ if ((upload_size + upload_alignment) <= STAGING_TEXTURE_UPLOAD_THRESHOLD &&
+ (upload_size + upload_alignment) <= MAXIMUM_TEXTURE_UPLOAD_BUFFER_SIZE)
+ {
+ // Assume tightly packed rows, with no padding as the buffer source.
+ StreamBuffer* upload_buffer = m_parent->m_texture_upload_buffer.get();
+
+ // Allocate memory from the streaming buffer for the texture data.
+ if (!upload_buffer->ReserveMemory(upload_size, g_vulkan_context->GetBufferImageGranularity()))
+ {
+ // Execute the command buffer first.
+ WARN_LOG(VIDEO, "Executing command list while waiting for space in texture upload buffer");
+ Util::ExecuteCurrentCommandsAndRestoreState(m_parent->m_state_tracker, false);
+
+ // Try allocating again. This may cause a fence wait.
+ if (!upload_buffer->ReserveMemory(upload_size, g_vulkan_context->GetBufferImageGranularity()))
+ PanicAlert("Failed to allocate space in texture upload buffer");
+ }
+
+ // Grab buffer pointers
+ VkBuffer image_upload_buffer = upload_buffer->GetBuffer();
+ VkDeviceSize image_upload_buffer_offset = upload_buffer->GetCurrentOffset();
+ u8* image_upload_buffer_pointer = upload_buffer->GetCurrentHostPointer();
+
+ // Copy to the buffer using the stride from the subresource layout
+ const u8* source_ptr = TextureCache::temp;
+ if (upload_pitch != source_pitch)
+ {
+ VkDeviceSize copy_pitch = std::min(source_pitch, upload_pitch);
+ for (unsigned int row = 0; row < height; row++)
+ {
+ memcpy(image_upload_buffer_pointer + row * upload_pitch, source_ptr + row * source_pitch,
+ copy_pitch);
+ }
+ }
+ else
+ {
+ // Can copy the whole thing in one block, the pitch matches
+ memcpy(image_upload_buffer_pointer, source_ptr, upload_size);
+ }
+
+ // Flush buffer memory if necessary
+ upload_buffer->CommitMemory(upload_size);
+
+ // Copy from the streaming buffer to the actual image.
+ VkBufferImageCopy image_copy = {
+ image_upload_buffer_offset, // VkDeviceSize bufferOffset
+ 0, // uint32_t bufferRowLength
+ 0, // uint32_t bufferImageHeight
+ {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, 1}, // VkImageSubresourceLayers imageSubresource
+ {0, 0, 0}, // VkOffset3D imageOffset
+ {width, height, 1} // VkExtent3D imageExtent
+ };
+ vkCmdCopyBufferToImage(g_command_buffer_mgr->GetCurrentInitCommandBuffer(), image_upload_buffer,
+ m_texture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
+ &image_copy);
+ }
+ else
+ {
+ // Slow path. The data for the image is too large to fit in the streaming buffer, so we need
+ // to allocate a temporary texture to store the data in, then copy to the real texture.
+ std::unique_ptr<StagingTexture2D> staging_texture = StagingTexture2D::Create(
+ STAGING_BUFFER_TYPE_UPLOAD, width, height, TEXTURECACHE_TEXTURE_FORMAT);
+
+ if (!staging_texture || !staging_texture->Map())
+ {
+ PanicAlert("Failed to allocate staging texture for large texture upload.");
+ return;
+ }
+
+ // Copy data to staging texture first, then to the "real" texture.
+ staging_texture->WriteTexels(0, 0, width, height, TextureCache::temp, source_pitch);
+ staging_texture->CopyToImage(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
+ m_texture->GetImage(), VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, width,
+ height, level, 0);
+ }
+
+ // Transition to shader read only.
+ barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+ barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+ barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ vkCmdPipelineBarrier(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
+ VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0,
+ nullptr, 0, nullptr, 1, &barrier);
+ m_texture->OverrideImageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+}
+
+void TextureCache::TCacheEntry::FromRenderTarget(u8* dst, PEControl::PixelFormat src_format,
+ const EFBRectangle& src_rect, bool scale_by_half,
+ unsigned int cbufid, const float* colmat)
+{
+ // A better way of doing this would be nice.
+ FramebufferManager* framebuffer_mgr =
+ static_cast<FramebufferManager*>(g_framebuffer_manager.get());
+ TargetRectangle scaled_src_rect = g_renderer->ConvertEFBRectangle(src_rect);
+ bool is_depth_copy = (src_format == PEControl::Z24);
+
+ // Flush EFB pokes first, as they're expected to be included.
+ framebuffer_mgr->FlushEFBPokes(m_parent->m_state_tracker);
+
+ // Has to be flagged as a render target.
+ _assert_(m_framebuffer != VK_NULL_HANDLE);
+
+ // Can't be done in a render pass, since we're doing our own render pass!
+ StateTracker* state_tracker = m_parent->m_state_tracker;
+ VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer();
+ state_tracker->EndRenderPass();
+
+ // Transition EFB to shader resource before binding
+ VkRect2D region = {{scaled_src_rect.left, scaled_src_rect.top},
+ {static_cast<u32>(scaled_src_rect.GetWidth()),
+ static_cast<u32>(scaled_src_rect.GetHeight())}};
+ Texture2D* src_texture = is_depth_copy ?
+ framebuffer_mgr->ResolveEFBDepthTexture(state_tracker, region) :
+ framebuffer_mgr->ResolveEFBColorTexture(state_tracker, region);
+ VkSampler src_sampler =
+ scale_by_half ? g_object_cache->GetLinearSampler() : g_object_cache->GetPointSampler();
+ VkImageLayout original_layout = src_texture->GetLayout();
+ src_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+ UtilityShaderDraw draw(
+ command_buffer, g_object_cache->GetPushConstantPipelineLayout(),
+ m_parent->GetRenderPassForTextureUpdate(m_texture.get()),
+ g_object_cache->GetPassthroughVertexShader(), g_object_cache->GetPassthroughGeometryShader(),
+ is_depth_copy ? m_parent->m_efb_depth_to_tex_shader : m_parent->m_efb_color_to_tex_shader);
+
+ draw.SetPushConstants(colmat, (is_depth_copy ? sizeof(float) * 20 : sizeof(float) * 28));
+ draw.SetPSSampler(0, src_texture->GetView(), src_sampler);
+
+ VkRect2D dest_region = {{0, 0}, {m_texture->GetWidth(), m_texture->GetHeight()}};
+
+ draw.BeginRenderPass(m_framebuffer, dest_region);
+
+ draw.DrawQuad(0, 0, config.width, config.height, scaled_src_rect.left, scaled_src_rect.top, 0,
+ scaled_src_rect.GetWidth(), scaled_src_rect.GetHeight(),
+ framebuffer_mgr->GetEFBWidth(), framebuffer_mgr->GetEFBHeight());
+
+ draw.EndRenderPass();
+
+ // We touched everything, so put it back.
+ state_tracker->SetPendingRebind();
+
+ // Transition the EFB back to its original layout.
+ src_texture->TransitionToLayout(command_buffer, original_layout);
+
+ // Render pass transitions texture to SHADER_READ_ONLY.
+ m_texture->OverrideImageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+}
+
+void TextureCache::TCacheEntry::CopyRectangleFromTexture(const TCacheEntryBase* source,
+ const MathUtil::Rectangle<int>& src_rect,
+ const MathUtil::Rectangle<int>& dst_rect)
+{
+ const TCacheEntry* source_vk = static_cast<const TCacheEntry*>(source);
+ VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer();
+
+ // Fast path when not scaling the image.
+ if (src_rect.GetWidth() == dst_rect.GetWidth() && src_rect.GetHeight() == dst_rect.GetHeight())
+ {
+ // These assertions should hold true unless the base code is passing us sizes too large, in
+ // which case it should be fixed instead.
+ _assert_msg_(VIDEO, static_cast<u32>(src_rect.GetWidth()) <= source->config.width &&
+ static_cast<u32>(src_rect.GetHeight()) <= source->config.height,
+ "Source rect is too large for CopyRectangleFromTexture");
+
+ _assert_msg_(VIDEO, static_cast<u32>(dst_rect.GetWidth()) <= config.width &&
+ static_cast<u32>(dst_rect.GetHeight()) <= config.height,
+ "Dest rect is too large for CopyRectangleFromTexture");
+
+ VkImageCopy image_copy = {
+ {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0,
+ source->config.layers}, // VkImageSubresourceLayers srcSubresource
+ {src_rect.left, src_rect.top, 0}, // VkOffset3D srcOffset
+ {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0,
+ config.layers}, // VkImageSubresourceLayers dstSubresource
+ {dst_rect.left, dst_rect.top, 0}, // VkOffset3D dstOffset
+ {static_cast<uint32_t>(src_rect.GetWidth()), static_cast<uint32_t>(src_rect.GetHeight()),
+ 1} // VkExtent3D extent
+ };
+
+ // Must be called outside of a render pass.
+ m_parent->m_state_tracker->EndRenderPass();
+
+ source_vk->m_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+ m_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+
+ vkCmdCopyImage(command_buffer, source_vk->m_texture->GetImage(),
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_texture->GetImage(),
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
+
+ m_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+ source_vk->m_texture->TransitionToLayout(command_buffer,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+ return;
+ }
+
+ // Can't do this within a game render pass.
+ m_parent->m_state_tracker->EndRenderPass();
+ m_parent->m_state_tracker->SetPendingRebind();
+
+ // Can't render to a non-rendertarget (no framebuffer).
+ _assert_msg_(VIDEO, config.rendertarget,
+ "Destination texture for partial copy is not a rendertarget");
+
+ UtilityShaderDraw draw(
+ g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetStandardPipelineLayout(),
+ m_parent->GetRenderPassForTextureUpdate(m_texture.get()),
+ g_object_cache->GetPassthroughVertexShader(), VK_NULL_HANDLE, m_parent->m_copy_shader);
+
+ VkRect2D region = {
+ {dst_rect.left, dst_rect.top},
+ {static_cast<u32>(dst_rect.GetWidth()), static_cast<u32>(dst_rect.GetHeight())}};
+ draw.BeginRenderPass(m_framebuffer, region);
+ draw.SetPSSampler(0, source_vk->GetTexture()->GetView(), g_object_cache->GetLinearSampler());
+ draw.DrawQuad(dst_rect.left, dst_rect.top, dst_rect.GetWidth(), dst_rect.GetHeight(),
+ src_rect.left, src_rect.top, 0, src_rect.GetWidth(), src_rect.GetHeight(),
+ source->config.width, source->config.height);
+ draw.EndRenderPass();
+
+ // Render pass transitions texture to SHADER_READ_ONLY.
+ m_texture->OverrideImageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+}
+
+void TextureCache::TCacheEntry::Bind(unsigned int stage)
+{
+ m_parent->m_state_tracker->SetTexture(stage, m_texture->GetView());
+}
+
+bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int level)
+{
+ _assert_(level < config.levels);
+
+ // Determine dimensions of image we want to save.
+ u32 level_width = std::max(1u, config.width >> level);
+ u32 level_height = std::max(1u, config.height >> level);
+
+ // Use a temporary staging texture for the download. Certainly not optimal,
+ // but since we have to idle the GPU anyway it doesn't really matter.
+ std::unique_ptr<StagingTexture2D> staging_texture = StagingTexture2D::Create(
+ STAGING_BUFFER_TYPE_READBACK, level_width, level_height, TEXTURECACHE_TEXTURE_FORMAT);
+
+ // Transition image to transfer source, and invalidate the current state,
+ // since we'll be executing the command buffer.
+ m_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+ m_parent->m_state_tracker->EndRenderPass();
+
+ // Copy to download buffer.
+ staging_texture->CopyFromImage(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ m_texture->GetImage(), VK_IMAGE_ASPECT_COLOR_BIT, 0, 0,
+ level_width, level_height, level, 0);
+
+ // Restore original state of texture.
+ m_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+ // Block until the GPU has finished copying to the staging texture.
+ g_command_buffer_mgr->ExecuteCommandBuffer(false, true);
+ m_parent->m_state_tracker->InvalidateDescriptorSets();
+ m_parent->m_state_tracker->SetPendingRebind();
+
+ // Map the staging texture so we can copy the contents out.
+ if (staging_texture->Map())
+ {
+ PanicAlert("Failed to map staging texture");
+ return false;
+ }
+
+ // Write texture out to file.
+ // It's okay to throw this texture away immediately, since we're done with it, and
+ // we blocked until the copy completed on the GPU anyway.
+ bool result = TextureToPng(reinterpret_cast<u8*>(staging_texture->GetMapPointer()),
+ staging_texture->GetRowStride(), filename, level_width, level_height);
+
+ staging_texture->Unmap();
+ return result;
+}
+
+bool TextureCache::CompileShaders()
+{
+ static const char COPY_SHADER_SOURCE[] = R"(
+ layout(set = 1, binding = 0) uniform sampler2DArray samp0;
+
+ layout(location = 0) in float3 uv0;
+ layout(location = 1) in float4 col0;
+ layout(location = 0) out float4 ocol0;
+
+ void main()
+ {
+ ocol0 = texture(samp0, uv0);
+ }
+ )";
+
+ static const char EFB_COLOR_TO_TEX_SOURCE[] = R"(
+ SAMPLER_BINDING(0) uniform sampler2DArray samp0;
+
+ layout(std140, push_constant) uniform PSBlock
+ {
+ vec4 colmat[7];
+ } C;
+
+ layout(location = 0) in vec3 uv0;
+ layout(location = 1) in vec4 col0;
+ layout(location = 0) out vec4 ocol0;
+
+ void main()
+ {
+ float4 texcol = texture(samp0, uv0);
+ texcol = round(texcol * C.colmat[5]) * C.colmat[6];
+ ocol0 = texcol * mat4(C.colmat[0], C.colmat[1], C.colmat[2], C.colmat[3]) + C.colmat[4];
+ }
+ )";
+
+ static const char EFB_DEPTH_TO_TEX_SOURCE[] = R"(
+ SAMPLER_BINDING(0) uniform sampler2DArray samp0;
+
+ layout(std140, push_constant) uniform PSBlock
+ {
+ vec4 colmat[5];
+ } C;
+
+ layout(location = 0) in vec3 uv0;
+ layout(location = 1) in vec4 col0;
+ layout(location = 0) out vec4 ocol0;
+
+ void main()
+ {
+ #if MONO_DEPTH
+ vec4 texcol = texture(samp0, vec3(uv0.xy, 0.0f));
+ #else
+ vec4 texcol = texture(samp0, uv0);
+ #endif
+ int depth = int((1.0 - texcol.x) * 16777216.0);
+
+ // Convert to Z24 format
+ ivec4 workspace;
+ workspace.r = (depth >> 16) & 255;
+ workspace.g = (depth >> 8) & 255;
+ workspace.b = depth & 255;
+
+ // Convert to Z4 format
+ workspace.a = (depth >> 16) & 0xF0;
+
+ // Normalize components to [0.0..1.0]
+ texcol = vec4(workspace) / 255.0;
+
+ ocol0 = texcol * mat4(C.colmat[0], C.colmat[1], C.colmat[2], C.colmat[3]) + C.colmat[4];
+ }
+ )";
+
+ std::string header = g_object_cache->GetUtilityShaderHeader();
+ std::string source;
+
+ source = header + COPY_SHADER_SOURCE;
+ m_copy_shader = Util::CompileAndCreateFragmentShader(source);
+
+ source = header + EFB_COLOR_TO_TEX_SOURCE;
+ m_efb_color_to_tex_shader = Util::CompileAndCreateFragmentShader(source);
+
+ if (g_ActiveConfig.bStereoEFBMonoDepth)
+ source = header + "#define MONO_DEPTH 1\n" + EFB_DEPTH_TO_TEX_SOURCE;
+ else
+ source = header + EFB_DEPTH_TO_TEX_SOURCE;
+ m_efb_depth_to_tex_shader = Util::CompileAndCreateFragmentShader(source);
+
+ return (m_copy_shader != VK_NULL_HANDLE && m_efb_color_to_tex_shader != VK_NULL_HANDLE &&
+ m_efb_depth_to_tex_shader != VK_NULL_HANDLE);
+}
+
+void TextureCache::DeleteShaders()
+{
+ auto DestroyShader = [this](VkShaderModule& shader) {
+ if (shader != VK_NULL_HANDLE)
+ {
+ vkDestroyShaderModule(g_vulkan_context->GetDevice(), shader, nullptr);
+ shader = VK_NULL_HANDLE;
+ }
+ };
+
+ // Since this can be called by the base class we need to wait for idle.
+ g_command_buffer_mgr->WaitForGPUIdle();
+
+ DestroyShader(m_copy_shader);
+ DestroyShader(m_efb_color_to_tex_shader);
+ DestroyShader(m_efb_depth_to_tex_shader);
+}
+
+} // namespace Vulkan