diff --git a/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp b/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp index 9e3245b27e..c3fc47cb5e 100644 --- a/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp @@ -10,6 +10,8 @@ #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" @@ -1365,4 +1367,96 @@ void FramebufferManager::DestroyPokeShaders() } } +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); + + // 2 bytes per pixel, so divide fb_stride by 2 to get the width. + TextureCache::GetInstance()->EncodeYUYVTextureToMemory(xfb_ptr, fb_stride / 2, 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 diff --git a/Source/Core/VideoBackends/Vulkan/FramebufferManager.h b/Source/Core/VideoBackends/Vulkan/FramebufferManager.h index 00fd335f49..92a689fb41 100644 --- a/Source/Core/VideoBackends/Vulkan/FramebufferManager.h +++ b/Source/Core/VideoBackends/Vulkan/FramebufferManager.h @@ -8,6 +8,7 @@ #include "Common/CommonTypes.h" #include "VideoBackends/Vulkan/Constants.h" +#include "VideoBackends/Vulkan/TextureCache.h" #include "VideoCommon/FramebufferManagerBase.h" namespace Vulkan @@ -17,12 +18,7 @@ class StateTracker; class StreamBuffer; class Texture2D; class VertexFormat; - -class XFBSource : public XFBSourceBase -{ - void DecodeToTexture(u32 xfb_addr, u32 fb_width, u32 fb_height) override {} - void CopyEFB(float gamma) override {} -}; +class XFBSource; class FramebufferManager : public FramebufferManagerBase { @@ -47,15 +43,11 @@ public: std::unique_ptr CreateXFBSource(unsigned int target_width, unsigned int target_height, - unsigned int layers) override - { - return std::make_unique(); - } + unsigned int layers) override; + // GPU EFB textures -> Guest void CopyToRealXFB(u32 xfb_addr, u32 fb_stride, u32 fb_height, const EFBRectangle& source_rc, - float gamma = 1.0f) override - { - } + float gamma = 1.0f) override; void ResizeEFBTextures(); @@ -175,4 +167,24 @@ private: VkShaderModule m_poke_fragment_shader = VK_NULL_HANDLE; }; +// The XFB source class simply wraps a texture cache entry. +// All the required functionality is provided by TextureCache. +class XFBSource final : public XFBSourceBase +{ +public: + explicit XFBSource(std::unique_ptr texture); + ~XFBSource(); + + TextureCache::TCacheEntry* GetTexture() const { return m_texture.get(); } + // Guest -> GPU EFB Textures + void DecodeToTexture(u32 xfb_addr, u32 fb_width, u32 fb_height) override; + + // Used for virtual XFB + void CopyEFB(float gamma) override; + +private: + std::unique_ptr m_texture; + VkFramebuffer m_framebuffer; +}; + } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp index 714a021699..959b9e405f 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp @@ -13,6 +13,7 @@ #include "Common/MsgHandler.h" #include "Core/ConfigManager.h" +#include "Core/Core.h" #include "VideoBackends/Vulkan/BoundingBox.h" #include "VideoBackends/Vulkan/CommandBufferManager.h" @@ -467,27 +468,31 @@ void Renderer::ReinterpretPixelData(unsigned int convtype) void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, const EFBRectangle& rc, u64 ticks, float gamma) { - // Flush any pending EFB pokes. + // Pending/batched EFB pokes should be included in the final image. FramebufferManager::GetInstance()->FlushEFBPokes(); + // Check that we actually have an image to render in XFB-on modes. + if ((!XFBWrited && !g_ActiveConfig.RealXFBEnabled()) || !fb_width || !fb_height) + { + Core::Callback_VideoCopiedToXFB(false); + return; + } + u32 xfb_count = 0; + const XFBSourceBase* const* xfb_sources = + FramebufferManager::GetXFBSource(xfb_addr, fb_stride, fb_height, &xfb_count); + if (g_ActiveConfig.VirtualXFBEnabled() && (!xfb_sources || xfb_count == 0)) + { + Core::Callback_VideoCopiedToXFB(false); + return; + } + // End the current render pass. StateTracker::GetInstance()->EndRenderPass(); StateTracker::GetInstance()->OnEndFrame(); - // Scale the source rectangle to the selected internal resolution. - TargetRectangle source_rc = Renderer::ConvertEFBRectangle(rc); - - // Transition the EFB render target to a shader resource. - VkRect2D src_region = {{0, 0}, - {FramebufferManager::GetInstance()->GetEFBWidth(), - FramebufferManager::GetInstance()->GetEFBHeight()}}; - Texture2D* efb_color_texture = - FramebufferManager::GetInstance()->ResolveEFBColorTexture(src_region); - efb_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - // Draw to the screenshot buffer if needed. - if (IsFrameDumping() && DrawScreenshot(source_rc, efb_color_texture)) + if (IsFrameDumping() && + DrawScreenshot(rc, xfb_addr, xfb_sources, xfb_count, fb_width, fb_stride, fb_height)) { DumpFrameData(reinterpret_cast(m_screenshot_readback_texture->GetMapPointer()), static_cast(m_screenshot_render_texture->GetWidth()), @@ -496,10 +501,6 @@ void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height FinishFrameData(); } - // Restore the EFB color texture to color attachment ready for rendering the next frame. - FramebufferManager::GetInstance()->GetEFBColorTexture()->TransitionToLayout( - g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - // Ensure the worker thread is not still submitting a previous command buffer. // In other words, the last frame has been submitted (otherwise the next call would // be a race, as the image may not have been consumed yet). @@ -508,7 +509,7 @@ void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height // Draw to the screen if we have a swap chain. if (m_swap_chain) { - DrawScreen(source_rc, efb_color_texture); + DrawScreen(rc, xfb_addr, xfb_sources, xfb_count, fb_width, fb_stride, fb_height); // Submit the current command buffer, signaling rendering finished semaphore when it's done // Because this final command buffer is rendering to the swap chain, we need to wait for @@ -548,7 +549,97 @@ void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height TextureCacheBase::Cleanup(frameCount); } -void Renderer::DrawScreen(const TargetRectangle& src_rect, const Texture2D* src_tex) +void Renderer::DrawFrame(VkRenderPass render_pass, const EFBRectangle& rc, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height) +{ + if (!g_ActiveConfig.bUseXFB) + DrawEFB(render_pass, rc); + else if (!g_ActiveConfig.bUseRealXFB) + DrawVirtualXFB(render_pass, xfb_addr, xfb_sources, xfb_count, fb_width, fb_stride, fb_height); + else + DrawRealXFB(render_pass, xfb_sources, xfb_count, fb_width, fb_stride, fb_height); +} + +void Renderer::DrawEFB(VkRenderPass render_pass, const EFBRectangle& rc) +{ + // Scale the source rectangle to the selected internal resolution. + TargetRectangle scaled_rc = Renderer::ConvertEFBRectangle(rc); + scaled_rc.left = std::max(scaled_rc.left, 0); + scaled_rc.right = std::max(scaled_rc.right, 0); + scaled_rc.top = std::max(scaled_rc.top, 0); + scaled_rc.bottom = std::max(scaled_rc.bottom, 0); + + // Transition the EFB render target to a shader resource. + VkRect2D src_region = { + {0, 0}, {static_cast(scaled_rc.GetWidth()), static_cast(scaled_rc.GetHeight())}}; + Texture2D* efb_color_texture = + FramebufferManager::GetInstance()->ResolveEFBColorTexture(src_region); + efb_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + // Copy EFB -> backbuffer + BlitScreen(render_pass, GetTargetRectangle(), scaled_rc, efb_color_texture, true); + + // Restore the EFB color texture to color attachment ready for rendering the next frame. + if (efb_color_texture == FramebufferManager::GetInstance()->GetEFBColorTexture()) + { + FramebufferManager::GetInstance()->GetEFBColorTexture()->TransitionToLayout( + g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + } +} + +void Renderer::DrawVirtualXFB(VkRenderPass render_pass, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height) +{ + const TargetRectangle& target_rect = GetTargetRectangle(); + for (u32 i = 0; i < xfb_count; ++i) + { + const XFBSource* xfb_source = static_cast(xfb_sources[i]); + TargetRectangle source_rect = xfb_source->sourceRc; + TargetRectangle draw_rect; + + int xfb_width = static_cast(xfb_source->srcWidth); + int xfb_height = static_cast(xfb_source->srcHeight); + int h_offset = (static_cast(xfb_source->srcAddr) - static_cast(xfb_addr)) / + (static_cast(fb_stride) * 2); + draw_rect.top = + target_rect.top + h_offset * target_rect.GetHeight() / static_cast(fb_height); + draw_rect.bottom = + target_rect.top + + (h_offset + xfb_height) * target_rect.GetHeight() / static_cast(fb_height); + draw_rect.left = target_rect.left + + (target_rect.GetWidth() - + xfb_width * target_rect.GetWidth() / static_cast(fb_stride)) / + 2; + draw_rect.right = target_rect.left + + (target_rect.GetWidth() + + xfb_width * target_rect.GetWidth() / static_cast(fb_stride)) / + 2; + + source_rect.right -= Renderer::EFBToScaledX(fb_stride - fb_width); + BlitScreen(render_pass, draw_rect, source_rect, xfb_source->GetTexture()->GetTexture(), true); + } +} + +void Renderer::DrawRealXFB(VkRenderPass render_pass, const XFBSourceBase* const* xfb_sources, + u32 xfb_count, u32 fb_width, u32 fb_stride, u32 fb_height) +{ + const TargetRectangle& target_rect = GetTargetRectangle(); + for (u32 i = 0; i < xfb_count; ++i) + { + const XFBSource* xfb_source = static_cast(xfb_sources[i]); + TargetRectangle source_rect = xfb_source->sourceRc; + TargetRectangle draw_rect = target_rect; + source_rect.right -= fb_stride - fb_width; + BlitScreen(render_pass, draw_rect, source_rect, xfb_source->GetTexture()->GetTexture(), true); + } +} + +void Renderer::DrawScreen(const EFBRectangle& rc, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height) { // Grab the next image from the swap chain in preparation for drawing the window. VkResult res = m_swap_chain->AcquireNextImage(m_image_available_semaphore); @@ -569,32 +660,31 @@ void Renderer::DrawScreen(const TargetRectangle& src_rect, const Texture2D* src_ backbuffer->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - // Blit the EFB to the back buffer (Swap chain) - UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), - g_object_cache->GetStandardPipelineLayout(), m_swap_chain->GetRenderPass(), - g_object_cache->GetPassthroughVertexShader(), VK_NULL_HANDLE, - m_blit_fragment_shader); - - // Begin the present render pass + // Begin render pass for rendering to the swap chain. VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; - VkRect2D target_region = {{0, 0}, {backbuffer->GetWidth(), backbuffer->GetHeight()}}; - draw.BeginRenderPass(m_swap_chain->GetCurrentFramebuffer(), target_region, &clear_value); + VkRenderPassBeginInfo info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + nullptr, + m_swap_chain->GetRenderPass(), + m_swap_chain->GetCurrentFramebuffer(), + {{0, 0}, {backbuffer->GetWidth(), backbuffer->GetHeight()}}, + 1, + &clear_value}; + vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &info, + VK_SUBPASS_CONTENTS_INLINE); - // Copy EFB -> backbuffer - const TargetRectangle& dst_rect = GetTargetRectangle(); - BlitScreen(m_swap_chain->GetRenderPass(), dst_rect, src_rect, src_tex, true); + // Draw guest buffers (EFB or XFB) + DrawFrame(m_swap_chain->GetRenderPass(), rc, xfb_addr, xfb_sources, xfb_count, fb_width, + fb_stride, fb_height); - // OSD stuff + // Draw OSD Util::SetViewportAndScissor(g_command_buffer_mgr->GetCurrentCommandBuffer(), 0, 0, backbuffer->GetWidth(), backbuffer->GetHeight()); DrawDebugText(); - - // Do our OSD callbacks OSD::DoCallbacks(OSD::CallbackType::OnFrame); OSD::DrawMessages(); // End drawing to backbuffer - draw.EndRenderPass(); + vkCmdEndRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer()); // Transition the backbuffer to PRESENT_SRC to ensure all commands drawing // to it have finished before present. @@ -602,7 +692,9 @@ void Renderer::DrawScreen(const TargetRectangle& src_rect, const Texture2D* src_ VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); } -bool Renderer::DrawScreenshot(const TargetRectangle& src_rect, const Texture2D* src_tex) +bool Renderer::DrawScreenshot(const EFBRectangle& rc, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height) { // Draw the screenshot to an image containing only the active screen area, removing any // borders as a result of the game rendering in a different aspect ratio. @@ -631,8 +723,8 @@ bool Renderer::DrawScreenshot(const TargetRectangle& src_rect, const Texture2D* VK_SUBPASS_CONTENTS_INLINE); vkCmdClearAttachments(g_command_buffer_mgr->GetCurrentCommandBuffer(), 1, &clear_attachment, 1, &clear_rect); - BlitScreen(FramebufferManager::GetInstance()->GetColorCopyForReadbackRenderPass(), target_rect, - src_rect, src_tex, true); + DrawFrame(FramebufferManager::GetInstance()->GetColorCopyForReadbackRenderPass(), rc, xfb_addr, + xfb_sources, xfb_count, fb_width, fb_stride, fb_height); vkCmdEndRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer()); // Copy to the readback texture. diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.h b/Source/Core/VideoBackends/Vulkan/Renderer.h index a84449dbed..d8dbb29595 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.h +++ b/Source/Core/VideoBackends/Vulkan/Renderer.h @@ -12,6 +12,8 @@ #include "VideoBackends/Vulkan/Constants.h" #include "VideoCommon/RenderBase.h" +struct XFBSourceBase; + namespace Vulkan { class BoundingBox; @@ -88,10 +90,29 @@ private: bool CompileShaders(); void DestroyShaders(); - void DrawScreen(const TargetRectangle& src_rect, const Texture2D* src_tex); - bool DrawScreenshot(const TargetRectangle& src_rect, const Texture2D* src_tex); + // Draw either the EFB, or specified XFB sources to the currently-bound framebuffer. + void DrawFrame(VkRenderPass render_pass, const EFBRectangle& rc, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height); + void DrawEFB(VkRenderPass render_pass, const EFBRectangle& rc); + void DrawVirtualXFB(VkRenderPass render_pass, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height); + void DrawRealXFB(VkRenderPass render_pass, const XFBSourceBase* const* xfb_sources, u32 xfb_count, + u32 fb_width, u32 fb_stride, u32 fb_height); + + // Draw the frame, as well as the OSD to the swap chain. + void DrawScreen(const EFBRectangle& rc, u32 xfb_addr, const XFBSourceBase* const* xfb_sources, + u32 xfb_count, u32 fb_width, u32 fb_stride, u32 fb_height); + + // Draw the frame only to the screenshot buffer. + bool DrawScreenshot(const EFBRectangle& rc, u32 xfb_addr, const XFBSourceBase* const* xfb_sources, + u32 xfb_count, u32 fb_width, u32 fb_stride, u32 fb_height); + + // Copies/scales an image to the currently-bound framebuffer. void BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_rect, const TargetRectangle& src_rect, const Texture2D* src_tex, bool linear_filter); + bool ResizeScreenshotBuffer(u32 new_width, u32 new_height); void DestroyScreenshotResources(); diff --git a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp index 2ed5ca3704..d99200b203 100644 --- a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp +++ b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp @@ -759,6 +759,52 @@ bool TextureCache::CompileShaders() } )"; + static const char RGB_TO_YUYV_SHADER_SOURCE[] = R"( + SAMPLER_BINDING(0) uniform sampler2DArray source; + layout(location = 0) in vec3 uv0; + layout(location = 0) out vec4 ocol0; + + const vec3 y_const = vec3(0.257,0.504,0.098); + const vec3 u_const = vec3(-0.148,-0.291,0.439); + const vec3 v_const = vec3(0.439,-0.368,-0.071); + const vec4 const3 = vec4(0.0625,0.5,0.0625,0.5); + + void main() + { + vec3 c0 = texture(source, vec3(uv0.xy - dFdx(uv0.xy) * 0.25, 0.0)).rgb; + vec3 c1 = texture(source, vec3(uv0.xy + dFdx(uv0.xy) * 0.25, 0.0)).rgb; + vec3 c01 = (c0 + c1) * 0.5; + ocol0 = vec4(dot(c1, y_const), + dot(c01,u_const), + dot(c0,y_const), + dot(c01, v_const)) + const3; + } + )"; + + static const char YUYV_TO_RGB_SHADER_SOURCE[] = R"( + SAMPLER_BINDING(0) uniform sampler2D source; + layout(location = 0) in vec3 uv0; + layout(location = 0) out vec4 ocol0; + + void main() + { + ivec2 uv = ivec2(gl_FragCoord.xy); + vec4 c0 = texelFetch(source, ivec2(uv.x / 2, uv.y), 0); + + // The texture used to stage the upload is in BGRA order. + c0 = c0.zyxw; + + float y = mix(c0.r, c0.b, (uv.x & 1) == 1); + float yComp = 1.164 * (y - 0.0625); + float uComp = c0.g - 0.5; + float vComp = c0.a - 0.5; + ocol0 = vec4(yComp + (1.596 * vComp), + yComp - (0.813 * vComp) - (0.391 * uComp), + yComp + (2.018 * uComp), + 1.0); + } + )"; + std::string header = g_object_cache->GetUtilityShaderHeader(); std::string source; @@ -774,8 +820,14 @@ bool TextureCache::CompileShaders() source = header + EFB_DEPTH_TO_TEX_SOURCE; m_efb_depth_to_tex_shader = Util::CompileAndCreateFragmentShader(source); + source = header + RGB_TO_YUYV_SHADER_SOURCE; + m_rgb_to_yuyv_shader = Util::CompileAndCreateFragmentShader(source); + source = header + YUYV_TO_RGB_SHADER_SOURCE; + m_yuyv_to_rgb_shader = Util::CompileAndCreateFragmentShader(source); + return (m_copy_shader != VK_NULL_HANDLE && m_efb_color_to_tex_shader != VK_NULL_HANDLE && - m_efb_depth_to_tex_shader != VK_NULL_HANDLE); + m_efb_depth_to_tex_shader != VK_NULL_HANDLE && m_rgb_to_yuyv_shader != VK_NULL_HANDLE && + m_yuyv_to_rgb_shader != VK_NULL_HANDLE); } void TextureCache::DeleteShaders() @@ -799,6 +851,120 @@ void TextureCache::DeleteShaders() vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_efb_depth_to_tex_shader, nullptr); m_efb_depth_to_tex_shader = VK_NULL_HANDLE; } + if (m_rgb_to_yuyv_shader != VK_NULL_HANDLE) + { + vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_rgb_to_yuyv_shader, nullptr); + m_rgb_to_yuyv_shader = VK_NULL_HANDLE; + } + if (m_yuyv_to_rgb_shader != VK_NULL_HANDLE) + { + vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_yuyv_to_rgb_shader, nullptr); + m_yuyv_to_rgb_shader = VK_NULL_HANDLE; + } +} + +void TextureCache::EncodeYUYVTextureToMemory(void* dst_ptr, u32 dst_width, u32 dst_stride, + u32 dst_height, Texture2D* src_texture, + const MathUtil::Rectangle& src_rect) +{ + StateTracker::GetInstance()->EndRenderPass(); + + VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); + src_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + // Borrow framebuffer from EFB2RAM encoder. + Texture2D* encoding_texture = m_texture_encoder->GetEncodingTexture(); + StagingTexture2D* download_texture = m_texture_encoder->GetDownloadTexture(); + encoding_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + // Use fragment shader to convert RGBA to YUYV. + // Use linear sampler for downscaling. This texture is in BGRA order, so the data is already in + // the order the guest is expecting and we don't have to swap it at readback time. The width + // is halved because we're using an RGBA8 texture, but the YUYV data is two bytes per pixel. + u32 output_width = dst_width / 2; + UtilityShaderDraw draw(command_buffer, g_object_cache->GetStandardPipelineLayout(), + m_texture_encoder->GetEncodingRenderPass(), + g_object_cache->GetPassthroughVertexShader(), VK_NULL_HANDLE, + m_rgb_to_yuyv_shader); + VkRect2D region = {{0, 0}, {output_width, dst_height}}; + draw.BeginRenderPass(m_texture_encoder->GetEncodingTextureFramebuffer(), region); + draw.SetPSSampler(0, src_texture->GetView(), g_object_cache->GetPointSampler()); + draw.DrawQuad(0, 0, static_cast(output_width), static_cast(dst_height), src_rect.left, + src_rect.top, 0, src_rect.GetWidth(), src_rect.GetHeight(), + static_cast(src_texture->GetWidth()), + static_cast(src_texture->GetHeight())); + draw.EndRenderPass(); + + // Render pass transitions to TRANSFER_SRC. + encoding_texture->OverrideImageLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + + // Copy from encoding texture to download buffer. + download_texture->CopyFromImage(command_buffer, encoding_texture->GetImage(), + VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, output_width, dst_height, 0, 0); + Util::ExecuteCurrentCommandsAndRestoreState(false, true); + + // Finally, copy to guest memory. This may have a different stride. + download_texture->ReadTexels(0, 0, output_width, dst_height, dst_ptr, dst_stride); +} + +void TextureCache::DecodeYUYVTextureFromMemory(TCacheEntry* dst_texture, const void* src_ptr, + u32 src_width, u32 src_stride, u32 src_height) +{ + // Copies (and our decoding step) cannot be done inside a render pass. + StateTracker::GetInstance()->EndRenderPass(); + + // We share the upload buffer with normal textures here, since the XFB buffers aren't very large. + u32 upload_size = src_stride * src_height; + if (!m_texture_upload_buffer->ReserveMemory(upload_size, + g_vulkan_context->GetBufferImageGranularity())) + { + // Execute the command buffer first. + WARN_LOG(VIDEO, "Executing command list while waiting for space in texture upload buffer"); + Util::ExecuteCurrentCommandsAndRestoreState(false); + if (!m_texture_upload_buffer->ReserveMemory(upload_size, + g_vulkan_context->GetBufferImageGranularity())) + PanicAlert("Failed to allocate space in texture upload buffer"); + } + + // Assume that each source row is not padded. + _assert_(src_stride == (src_width * sizeof(u16))); + VkDeviceSize image_upload_buffer_offset = m_texture_upload_buffer->GetCurrentOffset(); + std::memcpy(m_texture_upload_buffer->GetCurrentHostPointer(), src_ptr, upload_size); + m_texture_upload_buffer->CommitMemory(upload_size); + + // Copy from the upload buffer to the intermediate texture. We borrow this from the encoder. + // The width is specified as half here because we have two pixels packed in each RGBA texel. + // In the future this could be skipped by reading the upload buffer as a uniform texel buffer. + VkBufferImageCopy image_copy = { + image_upload_buffer_offset, // VkDeviceSize bufferOffset + 0, // uint32_t bufferRowLength + 0, // uint32_t bufferImageHeight + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, // VkImageSubresourceLayers imageSubresource + {0, 0, 0}, // VkOffset3D imageOffset + {src_width / 2, src_height, 1} // VkExtent3D imageExtent + }; + VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); + Texture2D* intermediate_texture = m_texture_encoder->GetEncodingTexture(); + intermediate_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + vkCmdCopyBufferToImage(command_buffer, m_texture_upload_buffer->GetBuffer(), + intermediate_texture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, + &image_copy); + intermediate_texture->TransitionToLayout(command_buffer, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + dst_texture->GetTexture()->TransitionToLayout(command_buffer, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + // Convert from the YUYV data now in the intermediate texture to RGBA in the destination. + UtilityShaderDraw draw(command_buffer, g_object_cache->GetStandardPipelineLayout(), + m_texture_encoder->GetEncodingRenderPass(), + g_object_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE, + m_yuyv_to_rgb_shader); + VkRect2D region = {{0, 0}, {src_width, src_height}}; + draw.BeginRenderPass(dst_texture->GetFramebuffer(), region); + draw.SetViewportAndScissor(0, 0, static_cast(src_width), static_cast(src_height)); + draw.SetPSSampler(0, intermediate_texture->GetView(), g_object_cache->GetPointSampler()); + draw.DrawWithoutVertexBuffer(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 4); + draw.EndRenderPass(); } } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/TextureCache.h b/Source/Core/VideoBackends/Vulkan/TextureCache.h index 34654e5bc3..5da9515991 100644 --- a/Source/Core/VideoBackends/Vulkan/TextureCache.h +++ b/Source/Core/VideoBackends/Vulkan/TextureCache.h @@ -66,6 +66,14 @@ public: void CopyRectangleFromTexture(TCacheEntry* dst_texture, const MathUtil::Rectangle& dst_rect, Texture2D* src_texture, const MathUtil::Rectangle& src_rect); + // Encodes texture to guest memory in XFB (YUYV) format. + void EncodeYUYVTextureToMemory(void* dst_ptr, u32 dst_width, u32 dst_stride, u32 dst_height, + Texture2D* src_texture, const MathUtil::Rectangle& src_rect); + + // Decodes data from guest memory in XFB (YUYV) format to a RGBA format texture on the GPU. + void DecodeYUYVTextureFromMemory(TCacheEntry* dst_texture, const void* src_ptr, u32 src_width, + u32 src_stride, u32 src_height); + private: bool CreateRenderPasses(); VkRenderPass GetRenderPassForTextureUpdate(const Texture2D* texture) const; @@ -90,6 +98,8 @@ private: VkShaderModule m_copy_shader = VK_NULL_HANDLE; VkShaderModule m_efb_color_to_tex_shader = VK_NULL_HANDLE; VkShaderModule m_efb_depth_to_tex_shader = VK_NULL_HANDLE; + VkShaderModule m_rgb_to_yuyv_shader = VK_NULL_HANDLE; + VkShaderModule m_yuyv_to_rgb_shader = VK_NULL_HANDLE; }; } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/TextureEncoder.cpp b/Source/Core/VideoBackends/Vulkan/TextureEncoder.cpp index 0d58ae6a85..404b30b09c 100644 --- a/Source/Core/VideoBackends/Vulkan/TextureEncoder.cpp +++ b/Source/Core/VideoBackends/Vulkan/TextureEncoder.cpp @@ -197,7 +197,8 @@ bool TextureEncoder::CreateEncodingTexture() m_encoding_texture = Texture2D::Create( ENCODING_TEXTURE_WIDTH, ENCODING_TEXTURE_HEIGHT, 1, 1, ENCODING_TEXTURE_FORMAT, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT); + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); if (!m_encoding_texture) return false; diff --git a/Source/Core/VideoBackends/Vulkan/TextureEncoder.h b/Source/Core/VideoBackends/Vulkan/TextureEncoder.h index c1863a04b2..c3fa68c752 100644 --- a/Source/Core/VideoBackends/Vulkan/TextureEncoder.h +++ b/Source/Core/VideoBackends/Vulkan/TextureEncoder.h @@ -23,6 +23,10 @@ public: TextureEncoder(); ~TextureEncoder(); + VkRenderPass GetEncodingRenderPass() const { return m_encoding_render_pass; } + Texture2D* GetEncodingTexture() const { return m_encoding_texture.get(); } + VkFramebuffer GetEncodingTextureFramebuffer() const { return m_encoding_texture_framebuffer; } + StagingTexture2D* GetDownloadTexture() const { return m_download_texture.get(); } bool Initialize(); // Uses an encoding shader to copy src_texture to dest_ptr.