// Copyright 2016 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include "VideoBackends/Vulkan/TextureCache.h" #include #include #include #include #include "Common/Assert.h" #include "Common/CommonFuncs.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/FramebufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/Renderer.h" #include "VideoBackends/Vulkan/StateTracker.h" #include "VideoBackends/Vulkan/StreamBuffer.h" #include "VideoBackends/Vulkan/Texture2D.h" #include "VideoBackends/Vulkan/TextureConverter.h" #include "VideoBackends/Vulkan/Util.h" #include "VideoBackends/Vulkan/VKTexture.h" #include "VideoBackends/Vulkan/VulkanContext.h" #include "VideoCommon/ImageWrite.h" #include "VideoCommon/TextureConfig.h" namespace Vulkan { TextureCache::TextureCache() { } TextureCache::~TextureCache() { TextureCache::DeleteShaders(); } VkShaderModule TextureCache::GetCopyShader() const { return m_copy_shader; } StreamBuffer* TextureCache::GetTextureUploadBuffer() const { return m_texture_upload_buffer.get(); } TextureCache* TextureCache::GetInstance() { return static_cast(g_texture_cache.get()); } bool TextureCache::Initialize() { 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; } m_texture_converter = std::make_unique(); if (!m_texture_converter->Initialize()) { PanicAlert("Failed to initialize texture converter"); return false; } if (!CompileShaders()) { PanicAlert("Failed to compile one or more shaders"); return false; } return true; } void TextureCache::ConvertTexture(TCacheEntry* destination, TCacheEntry* source, const void* palette, TLUTFormat format) { m_texture_converter->ConvertTexture(destination, source, palette, format); // Ensure both textures remain in the SHADER_READ_ONLY layout so they can be bound. static_cast(source->texture.get()) ->GetRawTexIdentifier() ->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); static_cast(destination->texture.get()) ->GetRawTexIdentifier() ->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } void TextureCache::CopyEFB(u8* dst, const EFBCopyParams& params, u32 native_width, u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride, const EFBRectangle& src_rect, bool scale_by_half, float y_scale, float gamma, bool clamp_top, bool clamp_bottom, const CopyFilterCoefficientArray& filter_coefficients) { // Flush EFB pokes first, as they're expected to be included. FramebufferManager::GetInstance()->FlushEFBPokes(); // MSAA case where we need to resolve first. // An out-of-bounds source region is valid here, and fine for the draw (since it is converted // to texture coordinates), but it's not valid to resolve an out-of-range rectangle. TargetRectangle scaled_src_rect = g_renderer->ConvertEFBRectangle(src_rect); VkRect2D region = {{scaled_src_rect.left, scaled_src_rect.top}, {static_cast(scaled_src_rect.GetWidth()), static_cast(scaled_src_rect.GetHeight())}}; region = Util::ClampRect2D(region, FramebufferManager::GetInstance()->GetEFBWidth(), FramebufferManager::GetInstance()->GetEFBHeight()); Texture2D* src_texture; if (params.depth) src_texture = FramebufferManager::GetInstance()->ResolveEFBDepthTexture(region); else src_texture = FramebufferManager::GetInstance()->ResolveEFBColorTexture(region); // End render pass before barrier (since we have no self-dependencies). // The barrier has to happen after the render pass, not inside it, as we are going to be // reading from the texture immediately afterwards. StateTracker::GetInstance()->EndRenderPass(); StateTracker::GetInstance()->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_converter->EncodeTextureToMemory( src_texture->GetView(), dst, params, native_width, bytes_per_row, num_blocks_y, memory_stride, src_rect, scale_by_half, y_scale, gamma, clamp_top, clamp_bottom, filter_coefficients); // Transition back to original state src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), original_layout); } bool TextureCache::SupportsGPUTextureDecode(TextureFormat format, TLUTFormat palette_format) { return m_texture_converter->SupportsTextureDecoding(format, palette_format); } void TextureCache::DecodeTextureOnGPU(TCacheEntry* entry, u32 dst_level, const u8* data, size_t data_size, TextureFormat format, u32 width, u32 height, u32 aligned_width, u32 aligned_height, u32 row_stride, const u8* palette, TLUTFormat palette_format) { // Group compute shader dispatches together in the init command buffer. That way we don't have to // pay a penalty for switching from graphics->compute, or end/restart our render pass. VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentInitCommandBuffer(); m_texture_converter->DecodeTexture(command_buffer, entry, dst_level, data, data_size, format, width, height, aligned_width, aligned_height, row_stride, palette, palette_format); // Last mip level? Ensure the texture is ready for use. if (dst_level == (entry->GetNumLevels() - 1)) { static_cast(entry->texture.get()) ->GetRawTexIdentifier() ->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } } 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); } )"; std::string header = g_shader_cache->GetUtilityShaderHeader(); std::string source = header + COPY_SHADER_SOURCE; m_copy_shader = Util::CompileAndCreateFragmentShader(source); return m_copy_shader != VK_NULL_HANDLE; } void TextureCache::DeleteShaders() { // It is safe to destroy shader modules after they are consumed by creating a pipeline. // Therefore, no matter where this function is called from, it won't cause an issue due to // pending commands, although at the time of writing should only be called at the end of // a frame. See Vulkan spec, section 2.3.1. Object Lifetime. if (m_copy_shader != VK_NULL_HANDLE) { vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_copy_shader, nullptr); m_copy_shader = VK_NULL_HANDLE; } for (auto& shader : m_efb_copy_to_tex_shaders) { vkDestroyShaderModule(g_vulkan_context->GetDevice(), shader.second, nullptr); } m_efb_copy_to_tex_shaders.clear(); } void TextureCache::CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy, const EFBRectangle& src_rect, bool scale_by_half, EFBCopyFormat dst_format, bool is_intensity, float gamma, bool clamp_top, bool clamp_bottom, const CopyFilterCoefficientArray& filter_coefficients) { VKTexture* texture = static_cast(entry->texture.get()); // A better way of doing this would be nice. FramebufferManager* framebuffer_mgr = static_cast(g_framebuffer_manager.get()); TargetRectangle scaled_src_rect = g_renderer->ConvertEFBRectangle(src_rect); // Flush EFB pokes first, as they're expected to be included. framebuffer_mgr->FlushEFBPokes(); // Has to be flagged as a render target. ASSERT(texture->GetFramebuffer() != VK_NULL_HANDLE); // Can't be done in a render pass, since we're doing our own render pass! VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); StateTracker::GetInstance()->EndRenderPass(); // Fill uniform buffer. struct PixelUniforms { float filter_coefficients[3]; float gamma_rcp; float clamp_top; float clamp_bottom; float pixel_height; u32 padding; }; PixelUniforms uniforms; for (size_t i = 0; i < filter_coefficients.size(); i++) uniforms.filter_coefficients[i] = filter_coefficients[i]; uniforms.gamma_rcp = 1.0f / gamma; uniforms.clamp_top = clamp_top ? src_rect.top / float(EFB_HEIGHT) : 0.0f; uniforms.clamp_bottom = clamp_bottom ? src_rect.bottom / float(EFB_HEIGHT) : 1.0f; uniforms.pixel_height = g_ActiveConfig.bCopyEFBScaled ? 1.0f / g_renderer->GetTargetHeight() : 1.0f / EFB_HEIGHT; uniforms.padding = 0; // Transition EFB to shader resource before binding. // An out-of-bounds source region is valid here, and fine for the draw (since it is converted // to texture coordinates), but it's not valid to resolve an out-of-range rectangle. VkRect2D region = {{scaled_src_rect.left, scaled_src_rect.top}, {static_cast(scaled_src_rect.GetWidth()), static_cast(scaled_src_rect.GetHeight())}}; region = Util::ClampRect2D(region, FramebufferManager::GetInstance()->GetEFBWidth(), FramebufferManager::GetInstance()->GetEFBHeight()); Texture2D* src_texture; if (is_depth_copy) src_texture = FramebufferManager::GetInstance()->ResolveEFBDepthTexture(region); else src_texture = FramebufferManager::GetInstance()->ResolveEFBColorTexture(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); texture->GetRawTexIdentifier()->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); auto uid = TextureConversionShaderGen::GetShaderUid(dst_format, is_depth_copy, is_intensity, scale_by_half, NeedsCopyFilterInShader(filter_coefficients)); auto it = m_efb_copy_to_tex_shaders.emplace(uid, VkShaderModule(VK_NULL_HANDLE)); VkShaderModule& shader = it.first->second; bool created = it.second; if (created) { std::string source = g_shader_cache->GetUtilityShaderHeader(); source += TextureConversionShaderGen::GenerateShader(APIType::Vulkan, uid.GetUidData()).GetBuffer(); shader = Util::CompileAndCreateFragmentShader(source); } VkRenderPass render_pass = g_object_cache->GetRenderPass( texture->GetRawTexIdentifier()->GetFormat(), VK_FORMAT_UNDEFINED, texture->GetRawTexIdentifier()->GetSamples(), VK_ATTACHMENT_LOAD_OP_DONT_CARE); UtilityShaderDraw draw(command_buffer, g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), render_pass, g_shader_cache->GetPassthroughVertexShader(), g_shader_cache->GetPassthroughGeometryShader(), shader); u8* ubo_ptr = draw.AllocatePSUniforms(sizeof(PixelUniforms)); std::memcpy(ubo_ptr, &uniforms, sizeof(PixelUniforms)); draw.CommitPSUniforms(sizeof(PixelUniforms)); draw.SetPSSampler(0, src_texture->GetView(), src_sampler); VkRect2D dest_region = {{0, 0}, {texture->GetConfig().width, texture->GetConfig().height}}; draw.BeginRenderPass(texture->GetFramebuffer(), dest_region); draw.DrawQuad(0, 0, texture->GetConfig().width, texture->GetConfig().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. StateTracker::GetInstance()->SetPendingRebind(); // Transition the EFB back to its original layout. src_texture->TransitionToLayout(command_buffer, original_layout); // Ensure texture is in SHADER_READ_ONLY layout, ready for usage. texture->GetRawTexIdentifier()->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } } // namespace Vulkan