// Copyright 2016 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include "VideoBackends/Vulkan/FramebufferManager.h" #include #include #include "Common/CommonFuncs.h" #include "Common/Logging/Log.h" #include "Core/HW/Memmap.h" #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/StagingTexture2D.h" #include "VideoBackends/Vulkan/StateTracker.h" #include "VideoBackends/Vulkan/StreamBuffer.h" #include "VideoBackends/Vulkan/Texture2D.h" #include "VideoBackends/Vulkan/Util.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(); DestroyEFBRenderPass(); DestroyConversionShaders(); DestroyReadbackFramebuffer(); DestroyReadbackTextures(); DestroyReadbackShaders(); DestroyReadbackRenderPasses(); DestroyPokeVertexBuffer(); DestroyPokeShaders(); } FramebufferManager* FramebufferManager::GetInstance() { return static_cast(g_framebuffer_manager.get()); } bool FramebufferManager::Initialize() { if (!CreateEFBRenderPass()) { 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; } void FramebufferManager::GetTargetSize(unsigned int* width, unsigned int* height) { *width = m_efb_width; *height = m_efb_height; } bool FramebufferManager::CreateEFBRenderPass() { m_efb_samples = static_cast(g_ActiveConfig.iMultisamples); // render pass for rendering to the efb VkAttachmentDescription attachments[] = { {0, EFB_COLOR_TEXTURE_FORMAT, m_efb_samples, VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}, {0, EFB_DEPTH_TEXTURE_FORMAT, m_efb_samples, VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL}}; VkAttachmentReference color_attachment_references[] = { {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}}; VkAttachmentReference depth_attachment_reference = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL}; VkSubpassDescription subpass_description = { 0, VK_PIPELINE_BIND_POINT_GRAPHICS, 0, nullptr, 1, color_attachment_references, nullptr, &depth_attachment_reference, 0, nullptr}; VkRenderPassCreateInfo pass_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, nullptr, 0, static_cast(ArraySize(attachments)), attachments, 1, &subpass_description, 0, nullptr}; VkResult res = vkCreateRenderPass(g_vulkan_context->GetDevice(), &pass_info, nullptr, &m_efb_load_render_pass); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateRenderPass (EFB) failed: "); return false; } // render pass for clearing color/depth on load, as opposed to loading it attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; res = vkCreateRenderPass(g_vulkan_context->GetDevice(), &pass_info, nullptr, &m_efb_clear_render_pass); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateRenderPass (EFB) failed: "); return false; } // render pass for resolving depth, since we can't do it with vkCmdResolveImage if (m_efb_samples != VK_SAMPLE_COUNT_1_BIT) { VkAttachmentDescription resolve_attachment = {0, EFB_DEPTH_AS_COLOR_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_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; // Ensure all reads have finished from the resolved texture before overwriting it. VkSubpassDependency dependencies[] = { {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_COLOR_ATTACHMENT_OUTPUT_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}}; subpass_description.pDepthStencilAttachment = nullptr; pass_info.pAttachments = &resolve_attachment; pass_info.attachmentCount = 1; pass_info.dependencyCount = static_cast(ArraySize(dependencies)); pass_info.pDependencies = dependencies; res = vkCreateRenderPass(g_vulkan_context->GetDevice(), &pass_info, nullptr, &m_depth_resolve_render_pass); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateRenderPass (EFB depth resolve) failed: "); return false; } } return true; } void FramebufferManager::DestroyEFBRenderPass() { if (m_efb_load_render_pass != VK_NULL_HANDLE) { vkDestroyRenderPass(g_vulkan_context->GetDevice(), m_efb_load_render_pass, nullptr); m_efb_load_render_pass = VK_NULL_HANDLE; } if (m_efb_clear_render_pass != VK_NULL_HANDLE) { vkDestroyRenderPass(g_vulkan_context->GetDevice(), m_efb_clear_render_pass, nullptr); m_efb_clear_render_pass = VK_NULL_HANDLE; } if (m_depth_resolve_render_pass != VK_NULL_HANDLE) { vkDestroyRenderPass(g_vulkan_context->GetDevice(), m_depth_resolve_render_pass, nullptr); m_depth_resolve_render_pass = VK_NULL_HANDLE; } } bool FramebufferManager::CreateEFBFramebuffer() { m_efb_width = static_cast(std::max(Renderer::GetTargetWidth(), 1)); m_efb_height = static_cast(std::max(Renderer::GetTargetHeight(), 1)); m_efb_layers = (g_ActiveConfig.iStereoMode != STEREO_OFF) ? 2 : 1; INFO_LOG(VIDEO, "EFB size: %ux%ux%u", m_efb_width, m_efb_height, m_efb_layers); // Update the static variable in the base class. Why does this even exist? FramebufferManagerBase::m_EFBLayers = m_efb_layers; // Allocate EFB render targets m_efb_color_texture = Texture2D::Create(m_efb_width, m_efb_height, 1, m_efb_layers, EFB_COLOR_TEXTURE_FORMAT, m_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(m_efb_width, m_efb_height, 1, m_efb_layers, EFB_COLOR_TEXTURE_FORMAT, m_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( m_efb_width, m_efb_height, 1, m_efb_layers, EFB_DEPTH_TEXTURE_FORMAT, m_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 (m_efb_samples != VK_SAMPLE_COUNT_1_BIT) { m_efb_resolve_color_texture = Texture2D::Create( m_efb_width, m_efb_height, 1, m_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( m_efb_width, m_efb_height, 1, m_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, m_efb_width, m_efb_height, m_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, m_efb_width, m_efb_height, m_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_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, 1.0f}}; static const VkClearDepthStencilValue clear_depth = {0.0f, 0}; VkImageSubresourceRange clear_color_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, m_efb_layers}; VkImageSubresourceRange clear_depth_range = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, m_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); 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::ResizeEFBTextures() { DestroyEFBFramebuffer(); if (!CreateEFBFramebuffer()) PanicAlert("Failed to create EFB textures"); } void FramebufferManager::RecreateRenderPass() { DestroyEFBRenderPass(); if (!CreateEFBRenderPass()) PanicAlert("Failed to create EFB render pass"); } 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->GetStandardPipelineLayout(), m_efb_load_render_pass, g_object_cache->GetScreenQuadVertexShader(), g_object_cache->GetScreenQuadGeometryShader(), pixel_shader); RasterizationState rs_state = Util::GetNoCullRasterizationState(); rs_state.samples = m_efb_samples; rs_state.per_sample_shading = g_ActiveConfig.bSSAA ? VK_TRUE : VK_FALSE; draw.SetRasterizationState(rs_state); VkRect2D region = {{0, 0}, {m_efb_width, m_efb_height}}; draw.BeginRenderPass(m_efb_convert_framebuffer, region); draw.SetPSSampler(0, m_efb_color_texture->GetView(), g_object_cache->GetPointSampler()); draw.SetViewportAndScissor(0, 0, m_efb_width, m_efb_height); draw.DrawWithoutVertexBuffer(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 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 (m_efb_samples == VK_SAMPLE_COUNT_1_BIT) return m_efb_color_texture.get(); // Can't resolve within a render pass. StateTracker::GetInstance()->EndRenderPass(); // 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, m_efb_layers}, // VkImageSubresourceLayers srcSubresource {region.offset.x, region.offset.y, 0}, // VkOffset3D srcOffset {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, m_efb_layers}, // VkImageSubresourceLayers dstSubresource {region.offset.x, region.offset.y, 0}, // VkOffset3D dstOffset {region.extent.width, region.extent.height, m_efb_layers} // 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 (m_efb_samples == 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); // 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->GetStandardPipelineLayout(), m_depth_resolve_render_pass, g_object_cache->GetScreenQuadVertexShader(), g_object_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(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 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); // Render pass transitions to shader resource. m_efb_resolve_depth_texture->OverrideImageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_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_object_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 (m_efb_samples != 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 && (m_efb_samples == 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, sizeof(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}, {m_efb_width, m_efb_height}}; Texture2D* src_texture = m_efb_color_texture.get(); VkImageAspectFlags src_aspect = VK_IMAGE_ASPECT_COLOR_BIT; if (m_efb_samples > 1) src_texture = ResolveEFBColorTexture(src_region); if (m_efb_width != EFB_WIDTH || m_efb_height != EFB_HEIGHT) { UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetStandardPipelineLayout(), m_copy_color_render_pass, g_object_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE, m_copy_color_shader); VkRect2D rect = {{0, 0}, {EFB_WIDTH, EFB_HEIGHT}}; draw.BeginRenderPass(m_color_copy_framebuffer, rect); // Transition EFB to shader read before drawing. src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); draw.SetPSSampler(0, src_texture->GetView(), g_object_cache->GetPointSampler()); draw.SetViewportAndScissor(0, 0, EFB_WIDTH, EFB_HEIGHT); draw.DrawWithoutVertexBuffer(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 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. m_color_copy_texture->OverrideImageLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); 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); m_color_readback_texture->CopyFromImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), src_texture->GetImage(), src_aspect, 0, 0, EFB_WIDTH, EFB_HEIGHT, 0, 0); // 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. g_command_buffer_mgr->ExecuteCommandBuffer(false, true); StateTracker::GetInstance()->InvalidateDescriptorSets(); StateTracker::GetInstance()->SetPendingRebind(); // Map to host memory. if (!m_color_readback_texture->IsMapped() && !m_color_readback_texture->Map()) return false; 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, sizeof(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}, {m_efb_width, m_efb_height}}; Texture2D* src_texture = m_efb_depth_texture.get(); VkImageAspectFlags src_aspect = VK_IMAGE_ASPECT_DEPTH_BIT; if (m_efb_samples > 1) { // EFB depth resolves are written out as color textures src_texture = ResolveEFBDepthTexture(src_region); src_aspect = VK_IMAGE_ASPECT_COLOR_BIT; } if (m_efb_width != EFB_WIDTH || m_efb_height != EFB_HEIGHT) { UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetStandardPipelineLayout(), m_copy_depth_render_pass, g_object_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE, m_copy_depth_shader); VkRect2D rect = {{0, 0}, {EFB_WIDTH, EFB_HEIGHT}}; draw.BeginRenderPass(m_depth_copy_framebuffer, rect); // Transition EFB to shader read before drawing. src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); draw.SetPSSampler(0, src_texture->GetView(), g_object_cache->GetPointSampler()); draw.SetViewportAndScissor(0, 0, EFB_WIDTH, EFB_HEIGHT); draw.DrawWithoutVertexBuffer(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 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. m_depth_copy_texture->OverrideImageLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); src_texture = m_depth_copy_texture.get(); src_aspect = VK_IMAGE_ASPECT_COLOR_BIT; } // Copy from EFB or copy texture to staging texture. src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); m_depth_readback_texture->CopyFromImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), src_texture->GetImage(), src_aspect, 0, 0, EFB_WIDTH, EFB_HEIGHT, 0, 0); // 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. g_command_buffer_mgr->ExecuteCommandBuffer(false, true); StateTracker::GetInstance()->InvalidateDescriptorSets(); StateTracker::GetInstance()->SetPendingRebind(); // Map to host memory. if (!m_depth_readback_texture->IsMapped() && !m_depth_readback_texture->Map()) return false; 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() { VkAttachmentDescription copy_attachment = { 0, // VkAttachmentDescriptionFlags flags EFB_COLOR_TEXTURE_FORMAT, // VkFormat format VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples VK_ATTACHMENT_LOAD_OP_DONT_CARE, // VkAttachmentLoadOp loadOp VK_ATTACHMENT_STORE_OP_STORE, // VkAttachmentStoreOp storeOp VK_ATTACHMENT_LOAD_OP_DONT_CARE, // VkAttachmentLoadOp stencilLoadOp VK_ATTACHMENT_STORE_OP_DONT_CARE, // VkAttachmentStoreOp stencilStoreOp VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, // VkImageLayout initialLayout VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL // VkImageLayout finalLayout }; VkAttachmentReference copy_attachment_ref = { 0, // uint32_t attachment VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL // VkImageLayout layout }; VkSubpassDescription copy_subpass = { 0, // VkSubpassDescriptionFlags flags VK_PIPELINE_BIND_POINT_GRAPHICS, // VkPipelineBindPoint pipelineBindPoint 0, // uint32_t inputAttachmentCount nullptr, // const VkAttachmentReference* pInputAttachments 1, // uint32_t colorAttachmentCount ©_attachment_ref, // const VkAttachmentReference* pColorAttachments nullptr, // const VkAttachmentReference* pResolveAttachments nullptr, // const VkAttachmentReference* pDepthStencilAttachment 0, // uint32_t preserveAttachmentCount nullptr // const uint32_t* pPreserveAttachments }; VkSubpassDependency copy_dependency = { 0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_DEPENDENCY_BY_REGION_BIT}; VkRenderPassCreateInfo copy_pass = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkRenderPassCreateFlags flags 1, // uint32_t attachmentCount ©_attachment, // const VkAttachmentDescription* pAttachments 1, // uint32_t subpassCount ©_subpass, // const VkSubpassDescription* pSubpasses 1, // uint32_t dependencyCount ©_dependency // const VkSubpassDependency* pDependencies }; VkResult res = vkCreateRenderPass(g_vulkan_context->GetDevice(), ©_pass, nullptr, &m_copy_color_render_pass); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateRenderPass failed: "); return false; } // Depth is similar to copy, just a different format. copy_attachment.format = EFB_DEPTH_AS_COLOR_TEXTURE_FORMAT; res = vkCreateRenderPass(g_vulkan_context->GetDevice(), ©_pass, nullptr, &m_copy_depth_render_pass); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateRenderPass failed: "); 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_topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; } else { // Points should be okay. m_poke_primitive_topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; } return true; } void FramebufferManager::DestroyReadbackRenderPasses() { if (m_copy_color_render_pass != VK_NULL_HANDLE) { vkDestroyRenderPass(g_vulkan_context->GetDevice(), m_copy_color_render_pass, nullptr); m_copy_color_render_pass = VK_NULL_HANDLE; } if (m_copy_depth_render_pass != VK_NULL_HANDLE) { vkDestroyRenderPass(g_vulkan_context->GetDevice(), m_copy_depth_render_pass, nullptr); m_copy_depth_render_pass = VK_NULL_HANDLE; } } 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_object_cache->GetUtilityShaderHeader() + COPY_COLOR_SHADER_SOURCE; m_copy_color_shader = Util::CompileAndCreateFragmentShader(source); source = g_object_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_color_readback_texture = StagingTexture2D::Create(STAGING_BUFFER_TYPE_READBACK, EFB_WIDTH, EFB_HEIGHT, EFB_COLOR_TEXTURE_FORMAT); if (!m_color_copy_texture || !m_color_readback_texture) { ERROR_LOG(VIDEO, "Failed to create EFB color readback texture"); return false; } 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); // We can't copy to/from color<->depth formats, so using a linear texture is not an option here. // TODO: Investigate if vkCmdBlitImage can be used. The documentation isn't that clear. m_depth_readback_texture = StagingTexture2DBuffer::Create(STAGING_BUFFER_TYPE_READBACK, EFB_WIDTH, EFB_HEIGHT, EFB_DEPTH_TEXTURE_FORMAT); if (!m_depth_copy_texture || !m_depth_readback_texture) { ERROR_LOG(VIDEO, "Failed to create EFB depth readback texture"); return false; } // With Vulkan, we can leave these textures mapped and use invalidate/flush calls instead. if (!m_color_readback_texture->Map() || !m_depth_readback_texture->Map()) { ERROR_LOG(VIDEO, "Failed to map EFB readback textures"); return false; } // Transition to TRANSFER_SRC, as this is expected by the render pass. m_color_copy_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); m_depth_copy_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); 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, sizeof(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, sizeof(depth)); } void FramebufferManager::CreatePokeVertices(std::vector* destination_list, u32 x, u32 y, float z, u32 color) { // Some devices don't support point sizes >1 (e.g. Adreno). if (m_poke_primitive_topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP) { // 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}); } else { // 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 = m_efb_width / static_cast(EFB_WIDTH); destination_list->push_back({{cs_x, cs_y, z, point_size}, 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->GetStandardPipelineLayout(); pipeline_info.vs = m_poke_vertex_shader; pipeline_info.gs = (m_efb_layers > 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.bits = Util::GetNoCullRasterizationState().bits; pipeline_info.rasterization_state.samples = m_efb_samples; pipeline_info.depth_stencil_state.bits = Util::GetNoDepthTestingDepthStencilState().bits; pipeline_info.blend_state.bits = Util::GetNoBlendingBlendState().bits; pipeline_info.blend_state.write_mask = 0; pipeline_info.primitive_topology = m_poke_primitive_topology; if (write_color) { pipeline_info.blend_state.write_mask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; } if (write_depth) { pipeline_info.depth_stencil_state.test_enable = VK_TRUE; pipeline_info.depth_stencil_state.write_enable = VK_TRUE; pipeline_info.depth_stencil_state.compare_op = VK_COMPARE_OP_ALWAYS; } VkPipeline pipeline = g_object_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(true); 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, m_efb_width, m_efb_height); 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; in VertexData { vec4 col0; } in_data[]; 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_object_cache->GetUtilityShaderHeader(); if (m_poke_primitive_topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST) 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_object_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_object_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; } } std::unique_ptr FramebufferManager::CreateXFBSource(unsigned int target_width, unsigned int target_height, unsigned int layers) { TextureCacheBase::TCacheEntryConfig config; config.width = target_width; config.height = target_height; config.layers = layers; config.rendertarget = true; auto* base_texture = TextureCache::GetInstance()->CreateTexture(config); auto* texture = static_cast(base_texture); if (!texture) { PanicAlert("Failed to create texture for XFB source"); return nullptr; } return std::make_unique(std::unique_ptr(texture)); } void FramebufferManager::CopyToRealXFB(u32 xfb_addr, u32 fb_stride, u32 fb_height, const EFBRectangle& source_rc, float gamma) { // Pending/batched EFB pokes should be included in the copied image. FlushEFBPokes(); // Schedule early command-buffer execution. StateTracker::GetInstance()->EndRenderPass(); StateTracker::GetInstance()->OnReadback(); // GPU EFB textures -> Guest memory u8* xfb_ptr = Memory::GetPointer(xfb_addr); _assert_(xfb_ptr); // source_rc is in native coordinates, so scale it to the internal resolution. TargetRectangle scaled_rc = g_renderer->ConvertEFBRectangle(source_rc); VkRect2D scaled_rc_vk = { {scaled_rc.left, scaled_rc.top}, {static_cast(scaled_rc.GetWidth()), static_cast(scaled_rc.GetHeight())}}; Texture2D* src_texture = ResolveEFBColorTexture(scaled_rc_vk); // The destination stride can differ from the copy region width, in which case the pixels // outside the copy region should not be written to. TextureCache::GetInstance()->EncodeYUYVTextureToMemory( xfb_ptr, static_cast(source_rc.GetWidth()), fb_stride, fb_height, src_texture, scaled_rc); // If we sourced directly from the EFB framebuffer, restore it to a color attachment. if (src_texture == m_efb_color_texture.get()) { src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); } } XFBSource::XFBSource(std::unique_ptr texture) : XFBSourceBase(), m_texture(std::move(texture)) { } XFBSource::~XFBSource() { } void XFBSource::DecodeToTexture(u32 xfb_addr, u32 fb_width, u32 fb_height) { // Guest memory -> GPU EFB Textures const u8* src_ptr = Memory::GetPointer(xfb_addr); _assert_(src_ptr); TextureCache::GetInstance()->DecodeYUYVTextureFromMemory(m_texture.get(), src_ptr, fb_width, fb_width * 2, fb_height); } void XFBSource::CopyEFB(float gamma) { // Pending/batched EFB pokes should be included in the copied image. FramebufferManager::GetInstance()->FlushEFBPokes(); // Virtual XFB, copy EFB at native resolution to m_texture MathUtil::Rectangle rect(0, 0, static_cast(texWidth), static_cast(texHeight)); VkRect2D vk_rect = {{rect.left, rect.top}, {static_cast(rect.GetWidth()), static_cast(rect.GetHeight())}}; Texture2D* src_texture = FramebufferManager::GetInstance()->ResolveEFBColorTexture(vk_rect); TextureCache::GetInstance()->CopyRectangleFromTexture(m_texture.get(), rect, src_texture, rect); // If we sourced directly from the EFB framebuffer, restore it to a color attachment. if (src_texture == FramebufferManager::GetInstance()->GetEFBColorTexture()) { src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); } } } // namespace Vulkan