// Copyright 2016 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include "VideoBackends/Vulkan/FramebufferManager.h" #include #include #include "Common/Assert.h" #include "Common/CommonFuncs.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Core/HW/Memmap.h" #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/StateTracker.h" #include "VideoBackends/Vulkan/StreamBuffer.h" #include "VideoBackends/Vulkan/Texture2D.h" #include "VideoBackends/Vulkan/Util.h" #include "VideoBackends/Vulkan/VKTexture.h" #include "VideoBackends/Vulkan/VertexFormat.h" #include "VideoBackends/Vulkan/VulkanContext.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/VideoConfig.h" namespace Vulkan { // Maximum number of pixels poked in one batch * 6 constexpr size_t MAX_POKE_VERTICES = 8192; constexpr size_t POKE_VERTEX_BUFFER_SIZE = 8 * 1024 * 1024; FramebufferManager::FramebufferManager() { } FramebufferManager::~FramebufferManager() { DestroyEFBFramebuffer(); DestroyConversionShaders(); DestroyReadbackFramebuffer(); DestroyReadbackTextures(); DestroyReadbackShaders(); DestroyPokeVertexBuffer(); DestroyPokeShaders(); } FramebufferManager* FramebufferManager::GetInstance() { return static_cast(g_framebuffer_manager.get()); } u32 FramebufferManager::GetEFBWidth() const { return m_efb_color_texture->GetWidth(); } u32 FramebufferManager::GetEFBHeight() const { return m_efb_color_texture->GetHeight(); } u32 FramebufferManager::GetEFBLayers() const { return m_efb_color_texture->GetLayers(); } VkSampleCountFlagBits FramebufferManager::GetEFBSamples() const { return m_efb_color_texture->GetSamples(); } MultisamplingState FramebufferManager::GetEFBMultisamplingState() const { MultisamplingState ms = {}; ms.per_sample_shading = g_ActiveConfig.MultisamplingEnabled() && g_ActiveConfig.bSSAA; ms.samples = static_cast(GetEFBSamples()); return ms; } bool FramebufferManager::Initialize() { if (!CreateEFBRenderPasses()) { PanicAlert("Failed to create EFB render pass"); return false; } if (!CreateEFBFramebuffer()) { PanicAlert("Failed to create EFB textures"); return false; } if (!CompileConversionShaders()) { PanicAlert("Failed to compile EFB shaders"); return false; } if (!CreateReadbackRenderPasses()) { PanicAlert("Failed to create readback render passes"); return false; } if (!CompileReadbackShaders()) { PanicAlert("Failed to compile readback shaders"); return false; } if (!CreateReadbackTextures()) { PanicAlert("Failed to create readback textures"); return false; } if (!CreateReadbackFramebuffer()) { PanicAlert("Failed to create readback framebuffer"); return false; } CreatePokeVertexFormat(); if (!CreatePokeVertexBuffer()) { PanicAlert("Failed to create poke vertex buffer"); return false; } if (!CompilePokeShaders()) { PanicAlert("Failed to compile poke shaders"); return false; } return true; } bool FramebufferManager::CreateEFBRenderPasses() { m_efb_load_render_pass = g_object_cache->GetRenderPass(EFB_COLOR_TEXTURE_FORMAT, EFB_DEPTH_TEXTURE_FORMAT, g_ActiveConfig.iMultisamples, VK_ATTACHMENT_LOAD_OP_LOAD); m_efb_clear_render_pass = g_object_cache->GetRenderPass(EFB_COLOR_TEXTURE_FORMAT, EFB_DEPTH_TEXTURE_FORMAT, g_ActiveConfig.iMultisamples, VK_ATTACHMENT_LOAD_OP_CLEAR); m_depth_resolve_render_pass = g_object_cache->GetRenderPass( EFB_DEPTH_AS_COLOR_TEXTURE_FORMAT, VK_FORMAT_UNDEFINED, 1, VK_ATTACHMENT_LOAD_OP_DONT_CARE); return m_efb_load_render_pass != VK_NULL_HANDLE && m_efb_clear_render_pass != VK_NULL_HANDLE && m_depth_resolve_render_pass != VK_NULL_HANDLE; } bool FramebufferManager::CreateEFBFramebuffer() { u32 efb_width = static_cast(std::max(g_renderer->GetTargetWidth(), 1)); u32 efb_height = static_cast(std::max(g_renderer->GetTargetHeight(), 1)); u32 efb_layers = (g_ActiveConfig.stereo_mode != StereoMode::Off) ? 2 : 1; VkSampleCountFlagBits efb_samples = static_cast(g_ActiveConfig.iMultisamples); INFO_LOG(VIDEO, "EFB size: %ux%ux%u", efb_width, efb_height, efb_layers); // Update the static variable in the base class. Why does this even exist? FramebufferManagerBase::m_EFBLayers = efb_layers; // Allocate EFB render targets m_efb_color_texture = Texture2D::Create(efb_width, efb_height, 1, efb_layers, EFB_COLOR_TEXTURE_FORMAT, efb_samples, VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); // We need a second texture to swap with for changing pixel formats m_efb_convert_color_texture = Texture2D::Create(efb_width, efb_height, 1, efb_layers, EFB_COLOR_TEXTURE_FORMAT, efb_samples, VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); m_efb_depth_texture = Texture2D::Create( efb_width, efb_height, 1, efb_layers, EFB_DEPTH_TEXTURE_FORMAT, efb_samples, VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); if (!m_efb_color_texture || !m_efb_convert_color_texture || !m_efb_depth_texture) return false; // Create resolved textures if MSAA is on if (g_ActiveConfig.MultisamplingEnabled()) { m_efb_resolve_color_texture = Texture2D::Create( efb_width, efb_height, 1, efb_layers, EFB_COLOR_TEXTURE_FORMAT, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); m_efb_resolve_depth_texture = Texture2D::Create( efb_width, efb_height, 1, efb_layers, EFB_DEPTH_AS_COLOR_TEXTURE_FORMAT, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); if (!m_efb_resolve_color_texture || !m_efb_resolve_depth_texture) return false; VkImageView attachment = m_efb_resolve_depth_texture->GetView(); VkFramebufferCreateInfo framebuffer_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, nullptr, 0, m_depth_resolve_render_pass, 1, &attachment, efb_width, efb_height, efb_layers}; VkResult res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &framebuffer_info, nullptr, &m_depth_resolve_framebuffer); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateFramebuffer failed: "); return false; } } VkImageView framebuffer_attachments[] = { m_efb_color_texture->GetView(), m_efb_depth_texture->GetView(), }; VkFramebufferCreateInfo framebuffer_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, nullptr, 0, m_efb_load_render_pass, static_cast(ArraySize(framebuffer_attachments)), framebuffer_attachments, efb_width, efb_height, efb_layers}; VkResult res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &framebuffer_info, nullptr, &m_efb_framebuffer); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateFramebuffer failed: "); return false; } // Create second framebuffer for format conversions framebuffer_attachments[0] = m_efb_convert_color_texture->GetView(); res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &framebuffer_info, nullptr, &m_efb_convert_framebuffer); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateFramebuffer failed: "); return false; } // Transition to state that can be used to clear m_efb_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); m_efb_convert_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); m_efb_depth_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); // Clear the contents of the buffers. static const VkClearColorValue clear_color = {{0.0f, 0.0f, 0.0f, 0.0f}}; static const VkClearDepthStencilValue clear_depth = {0.0f, 0}; VkImageSubresourceRange clear_color_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, efb_layers}; VkImageSubresourceRange clear_depth_range = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, efb_layers}; vkCmdClearColorImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_efb_color_texture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1, &clear_color_range); vkCmdClearColorImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_efb_convert_color_texture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1, &clear_color_range); vkCmdClearDepthStencilImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_efb_depth_texture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_depth, 1, &clear_depth_range); // Transition to color attachment state ready for rendering. m_efb_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); m_efb_depth_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); return true; } void FramebufferManager::DestroyEFBFramebuffer() { if (m_efb_framebuffer != VK_NULL_HANDLE) { vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_efb_framebuffer, nullptr); m_efb_framebuffer = VK_NULL_HANDLE; } if (m_efb_convert_framebuffer != VK_NULL_HANDLE) { vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_efb_convert_framebuffer, nullptr); m_efb_convert_framebuffer = VK_NULL_HANDLE; } if (m_depth_resolve_framebuffer != VK_NULL_HANDLE) { vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_depth_resolve_framebuffer, nullptr); m_depth_resolve_framebuffer = VK_NULL_HANDLE; } m_efb_color_texture.reset(); m_efb_convert_color_texture.reset(); m_efb_depth_texture.reset(); m_efb_resolve_color_texture.reset(); m_efb_resolve_depth_texture.reset(); } void FramebufferManager::RecreateEFBFramebuffer() { DestroyEFBFramebuffer(); if (!CreateEFBRenderPasses()) PanicAlert("Failed to create EFB render pass"); if (!CreateEFBFramebuffer()) PanicAlert("Failed to create EFB textures"); } void FramebufferManager::RecompileShaders() { DestroyConversionShaders(); if (!CompileConversionShaders()) PanicAlert("Failed to compile EFB shaders"); DestroyReadbackShaders(); if (!CompileReadbackShaders()) PanicAlert("Failed to compile readback shaders"); } void FramebufferManager::ReinterpretPixelData(int convtype) { VkShaderModule pixel_shader = VK_NULL_HANDLE; if (convtype == 0) { pixel_shader = m_ps_rgb8_to_rgba6; } else if (convtype == 2) { pixel_shader = m_ps_rgba6_to_rgb8; } else { ERROR_LOG(VIDEO, "Unhandled reinterpret pixel data %d", convtype); return; } // Transition EFB color buffer to shader resource, and the convert buffer to color attachment. m_efb_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); m_efb_convert_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), m_efb_load_render_pass, g_shader_cache->GetScreenQuadVertexShader(), g_shader_cache->GetScreenQuadGeometryShader(), pixel_shader); VkRect2D region = {{0, 0}, {GetEFBWidth(), GetEFBHeight()}}; draw.SetMultisamplingState(GetEFBMultisamplingState()); draw.BeginRenderPass(m_efb_convert_framebuffer, region); draw.SetPSSampler(0, m_efb_color_texture->GetView(), g_object_cache->GetPointSampler()); draw.SetViewportAndScissor(0, 0, GetEFBWidth(), GetEFBHeight()); draw.DrawWithoutVertexBuffer(4); draw.EndRenderPass(); // Swap EFB texture pointers std::swap(m_efb_color_texture, m_efb_convert_color_texture); std::swap(m_efb_framebuffer, m_efb_convert_framebuffer); } Texture2D* FramebufferManager::ResolveEFBColorTexture(const VkRect2D& region) { // Return the normal EFB texture if multisampling is off. if (GetEFBSamples() == VK_SAMPLE_COUNT_1_BIT) return m_efb_color_texture.get(); // Can't resolve within a render pass. StateTracker::GetInstance()->EndRenderPass(); // It's not valid to resolve out-of-bounds coordinates. // Ensuring the region is within the image is the caller's responsibility. ASSERT(region.offset.x >= 0 && region.offset.y >= 0 && (static_cast(region.offset.x) + region.extent.width) <= GetEFBWidth() && (static_cast(region.offset.y) + region.extent.height) <= GetEFBHeight()); // Resolving is considered to be a transfer operation. m_efb_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); m_efb_resolve_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); // Resolve to our already-created texture. VkImageResolve resolve = { {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, GetEFBLayers()}, // VkImageSubresourceLayers srcSubresource {region.offset.x, region.offset.y, 0}, // VkOffset3D srcOffset {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, GetEFBLayers()}, // VkImageSubresourceLayers dstSubresource {region.offset.x, region.offset.y, 0}, // VkOffset3D dstOffset {region.extent.width, region.extent.height, GetEFBLayers()} // VkExtent3D extent }; vkCmdResolveImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_efb_color_texture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_efb_resolve_color_texture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &resolve); // Restore MSAA texture ready for rendering again m_efb_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); return m_efb_resolve_color_texture.get(); } Texture2D* FramebufferManager::ResolveEFBDepthTexture(const VkRect2D& region) { // Return the normal EFB texture if multisampling is off. if (GetEFBSamples() == VK_SAMPLE_COUNT_1_BIT) return m_efb_depth_texture.get(); // Can't resolve within a render pass. StateTracker::GetInstance()->EndRenderPass(); m_efb_depth_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); m_efb_resolve_depth_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); // Draw using resolve shader to write the minimum depth of all samples to the resolve texture. UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), m_depth_resolve_render_pass, g_shader_cache->GetScreenQuadVertexShader(), g_shader_cache->GetScreenQuadGeometryShader(), m_ps_depth_resolve); draw.BeginRenderPass(m_depth_resolve_framebuffer, region); draw.SetPSSampler(0, m_efb_depth_texture->GetView(), g_object_cache->GetPointSampler()); draw.SetViewportAndScissor(region.offset.x, region.offset.y, region.extent.width, region.extent.height); draw.DrawWithoutVertexBuffer(4); draw.EndRenderPass(); // Restore MSAA texture ready for rendering again m_efb_depth_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); return m_efb_resolve_depth_texture.get(); } bool FramebufferManager::CompileConversionShaders() { static const char RGB8_TO_RGBA6_SHADER_SOURCE[] = R"( #if MSAA_ENABLED SAMPLER_BINDING(0) uniform sampler2DMSArray samp0; #else SAMPLER_BINDING(0) uniform sampler2DArray samp0; #endif layout(location = 0) in vec3 uv0; layout(location = 0) out vec4 ocol0; void main() { int layer = 0; #if EFB_LAYERS > 1 layer = int(uv0.z); #endif ivec3 coords = ivec3(gl_FragCoord.xy, layer); vec4 val; #if !MSAA_ENABLED // No MSAA - just load the first (and only) sample val = texelFetch(samp0, coords, 0); #elif SSAA_ENABLED // Sample shading, shader runs once per sample val = texelFetch(samp0, coords, gl_SampleID); #else // MSAA without sample shading, average out all samples. val = vec4(0, 0, 0, 0); for (int i = 0; i < MSAA_SAMPLES; i++) val += texelFetch(samp0, coords, i); val /= float(MSAA_SAMPLES); #endif ivec4 src8 = ivec4(round(val * 255.f)); ivec4 dst6; dst6.r = src8.r >> 2; dst6.g = ((src8.r & 0x3) << 4) | (src8.g >> 4); dst6.b = ((src8.g & 0xF) << 2) | (src8.b >> 6); dst6.a = src8.b & 0x3F; ocol0 = float4(dst6) / 63.f; } )"; static const char RGBA6_TO_RGB8_SHADER_SOURCE[] = R"( #if MSAA_ENABLED SAMPLER_BINDING(0) uniform sampler2DMSArray samp0; #else SAMPLER_BINDING(0) uniform sampler2DArray samp0; #endif layout(location = 0) in vec3 uv0; layout(location = 0) out vec4 ocol0; void main() { int layer = 0; #if EFB_LAYERS > 1 layer = int(uv0.z); #endif ivec3 coords = ivec3(gl_FragCoord.xy, layer); vec4 val; #if !MSAA_ENABLED // No MSAA - just load the first (and only) sample val = texelFetch(samp0, coords, 0); #elif SSAA_ENABLED // Sample shading, shader runs once per sample val = texelFetch(samp0, coords, gl_SampleID); #else // MSAA without sample shading, average out all samples. val = vec4(0, 0, 0, 0); for (int i = 0; i < MSAA_SAMPLES; i++) val += texelFetch(samp0, coords, i); val /= float(MSAA_SAMPLES); #endif ivec4 src6 = ivec4(round(val * 63.f)); ivec4 dst8; dst8.r = (src6.r << 2) | (src6.g >> 4); dst8.g = ((src6.g & 0xF) << 4) | (src6.b >> 2); dst8.b = ((src6.b & 0x3) << 6) | src6.a; dst8.a = 255; ocol0 = float4(dst8) / 255.f; } )"; static const char DEPTH_RESOLVE_SHADER_SOURCE[] = R"( SAMPLER_BINDING(0) uniform sampler2DMSArray samp0; layout(location = 0) in vec3 uv0; layout(location = 0) out float ocol0; void main() { int layer = 0; #if EFB_LAYERS > 1 layer = int(uv0.z); #endif // gl_FragCoord is in window coordinates, and we're rendering to // the same rectangle in the resolve texture. ivec3 coords = ivec3(gl_FragCoord.xy, layer); // Take the minimum of all depth samples. ocol0 = texelFetch(samp0, coords, 0).r; for (int i = 1; i < MSAA_SAMPLES; i++) ocol0 = min(ocol0, texelFetch(samp0, coords, i).r); } )"; std::string header = g_shader_cache->GetUtilityShaderHeader(); DestroyConversionShaders(); m_ps_rgb8_to_rgba6 = Util::CompileAndCreateFragmentShader(header + RGB8_TO_RGBA6_SHADER_SOURCE); m_ps_rgba6_to_rgb8 = Util::CompileAndCreateFragmentShader(header + RGBA6_TO_RGB8_SHADER_SOURCE); if (GetEFBSamples() != VK_SAMPLE_COUNT_1_BIT) m_ps_depth_resolve = Util::CompileAndCreateFragmentShader(header + DEPTH_RESOLVE_SHADER_SOURCE); return (m_ps_rgba6_to_rgb8 != VK_NULL_HANDLE && m_ps_rgb8_to_rgba6 != VK_NULL_HANDLE && (GetEFBSamples() == VK_SAMPLE_COUNT_1_BIT || m_ps_depth_resolve != VK_NULL_HANDLE)); } void FramebufferManager::DestroyConversionShaders() { auto DestroyShader = [this](VkShaderModule& shader) { if (shader != VK_NULL_HANDLE) { vkDestroyShaderModule(g_vulkan_context->GetDevice(), shader, nullptr); shader = VK_NULL_HANDLE; } }; DestroyShader(m_ps_rgb8_to_rgba6); DestroyShader(m_ps_rgba6_to_rgb8); DestroyShader(m_ps_depth_resolve); } u32 FramebufferManager::PeekEFBColor(u32 x, u32 y) { if (!m_color_readback_texture_valid && !PopulateColorReadbackTexture()) return 0; u32 value; m_color_readback_texture->ReadTexel(x, y, &value); return value; } bool FramebufferManager::PopulateColorReadbackTexture() { // Can't be in our normal render pass. StateTracker::GetInstance()->EndRenderPass(); StateTracker::GetInstance()->OnReadback(); // Issue a copy from framebuffer -> copy texture if we have >1xIR or MSAA on. VkRect2D src_region = {{0, 0}, {GetEFBWidth(), GetEFBHeight()}}; Texture2D* src_texture = m_efb_color_texture.get(); if (GetEFBSamples() > 1) src_texture = ResolveEFBColorTexture(src_region); if (GetEFBWidth() != EFB_WIDTH || GetEFBHeight() != EFB_HEIGHT) { // Transition EFB to shader read before drawing. src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); m_color_copy_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), m_copy_color_render_pass, g_shader_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE, m_copy_color_shader); VkRect2D rect = {{0, 0}, {EFB_WIDTH, EFB_HEIGHT}}; draw.BeginRenderPass(m_color_copy_framebuffer, rect); draw.SetPSSampler(0, src_texture->GetView(), g_object_cache->GetPointSampler()); draw.SetViewportAndScissor(0, 0, EFB_WIDTH, EFB_HEIGHT); draw.DrawWithoutVertexBuffer(4); draw.EndRenderPass(); // Restore EFB to color attachment, since we're done with it. if (src_texture == m_efb_color_texture.get()) { src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); } // Use this as a source texture now. src_texture = m_color_copy_texture.get(); } // Copy from EFB or copy texture to staging texture. src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); static_cast(m_color_readback_texture.get()) ->CopyFromTexture(src_texture, m_color_readback_texture->GetConfig().GetRect(), 0, 0, m_color_readback_texture->GetConfig().GetRect()); // Restore original layout if we used the EFB as a source. if (src_texture == m_efb_color_texture.get()) { src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); } // Wait until the copy is complete. m_color_readback_texture->Flush(); m_color_readback_texture_valid = true; return true; } float FramebufferManager::PeekEFBDepth(u32 x, u32 y) { if (!m_depth_readback_texture_valid && !PopulateDepthReadbackTexture()) return 0.0f; float value; m_depth_readback_texture->ReadTexel(x, y, &value); return value; } bool FramebufferManager::PopulateDepthReadbackTexture() { // Can't be in our normal render pass. StateTracker::GetInstance()->EndRenderPass(); StateTracker::GetInstance()->OnReadback(); // Issue a copy from framebuffer -> copy texture if we have >1xIR or MSAA on. VkRect2D src_region = {{0, 0}, {GetEFBWidth(), GetEFBHeight()}}; Texture2D* src_texture = m_efb_depth_texture.get(); if (GetEFBSamples() > 1) { // EFB depth resolves are written out as color textures src_texture = ResolveEFBDepthTexture(src_region); } if (GetEFBWidth() != EFB_WIDTH || GetEFBHeight() != EFB_HEIGHT) { // Transition EFB to shader read before drawing. src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); m_depth_copy_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), m_copy_depth_render_pass, g_shader_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE, m_copy_depth_shader); VkRect2D rect = {{0, 0}, {EFB_WIDTH, EFB_HEIGHT}}; draw.BeginRenderPass(m_depth_copy_framebuffer, rect); draw.SetPSSampler(0, src_texture->GetView(), g_object_cache->GetPointSampler()); draw.SetViewportAndScissor(0, 0, EFB_WIDTH, EFB_HEIGHT); draw.DrawWithoutVertexBuffer(4); draw.EndRenderPass(); // Restore EFB to depth attachment, since we're done with it. if (src_texture == m_efb_depth_texture.get()) { src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); } // Use this as a source texture now. src_texture = m_depth_copy_texture.get(); } // Copy from EFB or copy texture to staging texture. src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); static_cast(m_depth_readback_texture.get()) ->CopyFromTexture(src_texture, m_depth_readback_texture->GetConfig().GetRect(), 0, 0, m_depth_readback_texture->GetConfig().GetRect()); // Restore original layout if we used the EFB as a source. if (src_texture == m_efb_depth_texture.get()) { src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); } // Wait until the copy is complete. m_depth_readback_texture->Flush(); m_depth_readback_texture_valid = true; return true; } void FramebufferManager::InvalidatePeekCache() { m_color_readback_texture_valid = false; m_depth_readback_texture_valid = false; } bool FramebufferManager::CreateReadbackRenderPasses() { m_copy_color_render_pass = g_object_cache->GetRenderPass( EFB_COLOR_TEXTURE_FORMAT, VK_FORMAT_UNDEFINED, 1, VK_ATTACHMENT_LOAD_OP_DONT_CARE); m_copy_depth_render_pass = g_object_cache->GetRenderPass( EFB_DEPTH_AS_COLOR_TEXTURE_FORMAT, VK_FORMAT_UNDEFINED, 1, VK_ATTACHMENT_LOAD_OP_DONT_CARE); if (m_copy_color_render_pass == VK_NULL_HANDLE || m_copy_depth_render_pass == VK_NULL_HANDLE) return false; // Some devices don't support point sizes >1 (e.g. Adreno). // If we can't use a point size above our maximum IR, use triangles instead. // This means a 6x increase in the size of the vertices, though. if (!g_vulkan_context->GetDeviceFeatures().largePoints || g_vulkan_context->GetDeviceLimits().pointSizeGranularity > 1 || g_vulkan_context->GetDeviceLimits().pointSizeRange[0] > 1 || g_vulkan_context->GetDeviceLimits().pointSizeRange[1] < 16) { m_poke_primitive = PrimitiveType::TriangleStrip; } else { // Points should be okay. m_poke_primitive = PrimitiveType::Points; } return true; } bool FramebufferManager::CompileReadbackShaders() { std::string source; // TODO: Use input attachment here instead? // TODO: MSAA resolve in shader. static const char COPY_COLOR_SHADER_SOURCE[] = R"( SAMPLER_BINDING(0) uniform sampler2DArray samp0; layout(location = 0) in vec3 uv0; layout(location = 0) out vec4 ocol0; void main() { ocol0 = texture(samp0, uv0); } )"; static const char COPY_DEPTH_SHADER_SOURCE[] = R"( SAMPLER_BINDING(0) uniform sampler2DArray samp0; layout(location = 0) in vec3 uv0; layout(location = 0) out float ocol0; void main() { ocol0 = texture(samp0, uv0).r; } )"; source = g_shader_cache->GetUtilityShaderHeader() + COPY_COLOR_SHADER_SOURCE; m_copy_color_shader = Util::CompileAndCreateFragmentShader(source); source = g_shader_cache->GetUtilityShaderHeader() + COPY_DEPTH_SHADER_SOURCE; m_copy_depth_shader = Util::CompileAndCreateFragmentShader(source); return m_copy_color_shader != VK_NULL_HANDLE && m_copy_depth_shader != VK_NULL_HANDLE; } void FramebufferManager::DestroyReadbackShaders() { if (m_copy_color_shader != VK_NULL_HANDLE) { vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_copy_color_shader, nullptr); m_copy_color_shader = VK_NULL_HANDLE; } if (m_copy_depth_shader != VK_NULL_HANDLE) { vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_copy_depth_shader, nullptr); m_copy_depth_shader = VK_NULL_HANDLE; } } bool FramebufferManager::CreateReadbackTextures() { m_color_copy_texture = Texture2D::Create(EFB_WIDTH, EFB_HEIGHT, 1, 1, EFB_COLOR_TEXTURE_FORMAT, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); m_depth_copy_texture = Texture2D::Create(EFB_WIDTH, EFB_HEIGHT, 1, 1, EFB_DEPTH_AS_COLOR_TEXTURE_FORMAT, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); if (!m_color_copy_texture || !m_depth_copy_texture) { ERROR_LOG(VIDEO, "Failed to create EFB copy textures"); return false; } TextureConfig readback_texture_config(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, AbstractTextureFormat::RGBA8, false); m_color_readback_texture = g_renderer->CreateStagingTexture(StagingTextureType::Mutable, readback_texture_config); m_depth_readback_texture = g_renderer->CreateStagingTexture(StagingTextureType::Mutable, readback_texture_config); if (!m_color_readback_texture || !m_depth_readback_texture) { ERROR_LOG(VIDEO, "Failed to create EFB readback textures"); return false; } return true; } void FramebufferManager::DestroyReadbackTextures() { m_color_copy_texture.reset(); m_color_readback_texture.reset(); m_color_readback_texture_valid = false; m_depth_copy_texture.reset(); m_depth_readback_texture.reset(); m_depth_readback_texture_valid = false; } bool FramebufferManager::CreateReadbackFramebuffer() { VkImageView framebuffer_attachment = m_color_copy_texture->GetView(); VkFramebufferCreateInfo framebuffer_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkFramebufferCreateFlags flags m_copy_color_render_pass, // VkRenderPass renderPass 1, // uint32_t attachmentCount &framebuffer_attachment, // const VkImageView* pAttachments EFB_WIDTH, // uint32_t width EFB_HEIGHT, // uint32_t height 1 // uint32_t layers }; VkResult res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &framebuffer_info, nullptr, &m_color_copy_framebuffer); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateFramebuffer failed: "); return false; } // Swap for depth framebuffer_info.renderPass = m_copy_depth_render_pass; framebuffer_attachment = m_depth_copy_texture->GetView(); res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &framebuffer_info, nullptr, &m_depth_copy_framebuffer); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateFramebuffer failed: "); return false; } return true; } void FramebufferManager::DestroyReadbackFramebuffer() { if (m_color_copy_framebuffer != VK_NULL_HANDLE) { vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_color_copy_framebuffer, nullptr); m_color_copy_framebuffer = VK_NULL_HANDLE; } if (m_depth_copy_framebuffer != VK_NULL_HANDLE) { vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_depth_copy_framebuffer, nullptr); m_depth_copy_framebuffer = VK_NULL_HANDLE; } } void FramebufferManager::PokeEFBColor(u32 x, u32 y, u32 color) { // Flush if we exceeded the number of vertices per batch. if ((m_color_poke_vertices.size() + 6) > MAX_POKE_VERTICES) FlushEFBPokes(); CreatePokeVertices(&m_color_poke_vertices, x, y, 0.0f, color); // Update the peek cache if it's valid, since we know the color of the pixel now. if (m_color_readback_texture_valid) m_color_readback_texture->WriteTexel(x, y, &color); } void FramebufferManager::PokeEFBDepth(u32 x, u32 y, float depth) { // Flush if we exceeded the number of vertices per batch. if ((m_color_poke_vertices.size() + 6) > MAX_POKE_VERTICES) FlushEFBPokes(); CreatePokeVertices(&m_depth_poke_vertices, x, y, depth, 0); // Update the peek cache if it's valid, since we know the color of the pixel now. if (m_depth_readback_texture_valid) m_depth_readback_texture->WriteTexel(x, y, &depth); } void FramebufferManager::CreatePokeVertices(std::vector* destination_list, u32 x, u32 y, float z, u32 color) { if (m_poke_primitive == PrimitiveType::Points) { // GPU will expand the point to a quad. float cs_x = float(x) * 2.0f / EFB_WIDTH - 1.0f; float cs_y = float(y) * 2.0f / EFB_HEIGHT - 1.0f; float point_size = GetEFBWidth() / static_cast(EFB_WIDTH); destination_list->push_back({{cs_x, cs_y, z, point_size}, color}); return; } // Some devices don't support point sizes >1 (e.g. Adreno). // Generate quad from the single point (clip-space coordinates). float x1 = float(x) * 2.0f / EFB_WIDTH - 1.0f; float y1 = float(y) * 2.0f / EFB_HEIGHT - 1.0f; float x2 = float(x + 1) * 2.0f / EFB_WIDTH - 1.0f; float y2 = float(y + 1) * 2.0f / EFB_HEIGHT - 1.0f; destination_list->push_back({{x1, y1, z, 1.0f}, color}); destination_list->push_back({{x2, y1, z, 1.0f}, color}); destination_list->push_back({{x1, y2, z, 1.0f}, color}); destination_list->push_back({{x1, y2, z, 1.0f}, color}); destination_list->push_back({{x2, y1, z, 1.0f}, color}); destination_list->push_back({{x2, y2, z, 1.0f}, color}); } void FramebufferManager::FlushEFBPokes() { if (!m_color_poke_vertices.empty()) { DrawPokeVertices(m_color_poke_vertices.data(), m_color_poke_vertices.size(), true, false); m_color_poke_vertices.clear(); } if (!m_depth_poke_vertices.empty()) { DrawPokeVertices(m_depth_poke_vertices.data(), m_depth_poke_vertices.size(), false, true); m_depth_poke_vertices.clear(); } } void FramebufferManager::DrawPokeVertices(const EFBPokeVertex* vertices, size_t vertex_count, bool write_color, bool write_depth) { // Relatively simple since we don't have any bindings. VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); // We don't use the utility shader in order to keep the vertices compact. PipelineInfo pipeline_info = {}; pipeline_info.vertex_format = m_poke_vertex_format.get(); pipeline_info.pipeline_layout = g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD); pipeline_info.vs = m_poke_vertex_shader; pipeline_info.gs = (GetEFBLayers() > 1) ? m_poke_geometry_shader : VK_NULL_HANDLE; pipeline_info.ps = m_poke_fragment_shader; pipeline_info.render_pass = m_efb_load_render_pass; pipeline_info.rasterization_state.hex = RenderState::GetNoCullRasterizationState().hex; pipeline_info.rasterization_state.primitive = m_poke_primitive; pipeline_info.multisampling_state.hex = GetEFBMultisamplingState().hex; pipeline_info.depth_state.hex = RenderState::GetNoDepthTestingDepthStencilState().hex; pipeline_info.blend_state.hex = RenderState::GetNoBlendingBlendState().hex; pipeline_info.blend_state.colorupdate = write_color; pipeline_info.blend_state.alphaupdate = write_color; if (write_depth) { pipeline_info.depth_state.testenable = true; pipeline_info.depth_state.updateenable = true; pipeline_info.depth_state.func = ZMode::ALWAYS; } VkPipeline pipeline = g_shader_cache->GetPipeline(pipeline_info); if (pipeline == VK_NULL_HANDLE) { PanicAlert("Failed to get pipeline for EFB poke draw"); return; } // Populate vertex buffer. size_t vertices_size = sizeof(EFBPokeVertex) * m_color_poke_vertices.size(); if (!m_poke_vertex_stream_buffer->ReserveMemory(vertices_size, sizeof(EfbPokeData), true, true, false)) { // Kick a command buffer first. WARN_LOG(VIDEO, "Kicking command buffer due to no EFB poke space."); Util::ExecuteCurrentCommandsAndRestoreState(false); command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); if (!m_poke_vertex_stream_buffer->ReserveMemory(vertices_size, sizeof(EfbPokeData), true, true, false)) { PanicAlert("Failed to get space for EFB poke vertices"); return; } } VkBuffer vb_buffer = m_poke_vertex_stream_buffer->GetBuffer(); VkDeviceSize vb_offset = m_poke_vertex_stream_buffer->GetCurrentOffset(); memcpy(m_poke_vertex_stream_buffer->GetCurrentHostPointer(), vertices, vertices_size); m_poke_vertex_stream_buffer->CommitMemory(vertices_size); // Set up state. StateTracker::GetInstance()->EndClearRenderPass(); StateTracker::GetInstance()->BeginRenderPass(); StateTracker::GetInstance()->SetPendingRebind(); Util::SetViewportAndScissor(command_buffer, 0, 0, GetEFBWidth(), GetEFBHeight()); vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdBindVertexBuffers(command_buffer, 0, 1, &vb_buffer, &vb_offset); vkCmdDraw(command_buffer, static_cast(vertex_count), 1, 0, 0); } void FramebufferManager::CreatePokeVertexFormat() { PortableVertexDeclaration vtx_decl = {}; vtx_decl.position.enable = true; vtx_decl.position.type = VAR_FLOAT; vtx_decl.position.components = 4; vtx_decl.position.integer = false; vtx_decl.position.offset = offsetof(EFBPokeVertex, position); vtx_decl.colors[0].enable = true; vtx_decl.colors[0].type = VAR_UNSIGNED_BYTE; vtx_decl.colors[0].components = 4; vtx_decl.colors[0].integer = false; vtx_decl.colors[0].offset = offsetof(EFBPokeVertex, color); vtx_decl.stride = sizeof(EFBPokeVertex); m_poke_vertex_format = std::make_unique(vtx_decl); } bool FramebufferManager::CreatePokeVertexBuffer() { m_poke_vertex_stream_buffer = StreamBuffer::Create( VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, POKE_VERTEX_BUFFER_SIZE, POKE_VERTEX_BUFFER_SIZE); if (!m_poke_vertex_stream_buffer) { ERROR_LOG(VIDEO, "Failed to create EFB poke vertex buffer"); return false; } return true; } void FramebufferManager::DestroyPokeVertexBuffer() { m_poke_vertex_stream_buffer.reset(); } bool FramebufferManager::CompilePokeShaders() { static const char POKE_VERTEX_SHADER_SOURCE[] = R"( layout(location = 0) in vec4 ipos; layout(location = 5) in vec4 icol0; layout(location = 0) out vec4 col0; void main() { gl_Position = vec4(ipos.xyz, 1.0f); #if USE_POINT_SIZE gl_PointSize = ipos.w; #endif col0 = icol0; } )"; static const char POKE_GEOMETRY_SHADER_SOURCE[] = R"( layout(triangles) in; layout(triangle_strip, max_vertices = EFB_LAYERS * 3) out; VARYING_LOCATION(0) in VertexData { vec4 col0; } in_data[]; VARYING_LOCATION(0) out VertexData { vec4 col0; } out_data; void main() { for (int j = 0; j < EFB_LAYERS; j++) { for (int i = 0; i < 3; i++) { gl_Layer = j; gl_Position = gl_in[i].gl_Position; out_data.col0 = in_data[i].col0; EmitVertex(); } EndPrimitive(); } } )"; static const char POKE_PIXEL_SHADER_SOURCE[] = R"( layout(location = 0) in vec4 col0; layout(location = 0) out vec4 ocol0; void main() { ocol0 = col0; } )"; std::string source = g_shader_cache->GetUtilityShaderHeader(); if (m_poke_primitive == PrimitiveType::Points) source += "#define USE_POINT_SIZE 1\n"; source += POKE_VERTEX_SHADER_SOURCE; m_poke_vertex_shader = Util::CompileAndCreateVertexShader(source); if (m_poke_vertex_shader == VK_NULL_HANDLE) return false; if (g_vulkan_context->SupportsGeometryShaders()) { source = g_shader_cache->GetUtilityShaderHeader() + POKE_GEOMETRY_SHADER_SOURCE; m_poke_geometry_shader = Util::CompileAndCreateGeometryShader(source); if (m_poke_geometry_shader == VK_NULL_HANDLE) return false; } source = g_shader_cache->GetUtilityShaderHeader() + POKE_PIXEL_SHADER_SOURCE; m_poke_fragment_shader = Util::CompileAndCreateFragmentShader(source); if (m_poke_fragment_shader == VK_NULL_HANDLE) return false; return true; } void FramebufferManager::DestroyPokeShaders() { if (m_poke_vertex_shader != VK_NULL_HANDLE) { vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_poke_vertex_shader, nullptr); m_poke_vertex_shader = VK_NULL_HANDLE; } if (m_poke_geometry_shader != VK_NULL_HANDLE) { vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_poke_geometry_shader, nullptr); m_poke_geometry_shader = VK_NULL_HANDLE; } if (m_poke_fragment_shader != VK_NULL_HANDLE) { vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_poke_fragment_shader, nullptr); m_poke_vertex_shader = VK_NULL_HANDLE; } } } // namespace Vulkan