Vulkan: Implement virtual/real XFB support
This commit is contained in:
parent
3593fa27ab
commit
5182e6b549
|
@ -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<XFBSourceBase> 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<TextureCache::TCacheEntry*>(base_texture);
|
||||
if (!texture)
|
||||
{
|
||||
PanicAlert("Failed to create texture for XFB source");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<XFBSource>(std::unique_ptr<TextureCache::TCacheEntry>(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<u32>(scaled_rc.GetWidth()), static_cast<u32>(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<TextureCache::TCacheEntry> 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<int> rect(0, 0, static_cast<int>(texWidth), static_cast<int>(texHeight));
|
||||
VkRect2D vk_rect = {{rect.left, rect.top},
|
||||
{static_cast<u32>(rect.GetWidth()), static_cast<u32>(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
|
||||
|
|
|
@ -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<XFBSourceBase> CreateXFBSource(unsigned int target_width,
|
||||
unsigned int target_height,
|
||||
unsigned int layers) override
|
||||
{
|
||||
return std::make_unique<XFBSource>();
|
||||
}
|
||||
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<TextureCache::TCacheEntry> 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<TextureCache::TCacheEntry> m_texture;
|
||||
VkFramebuffer m_framebuffer;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
@ -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<const u8*>(m_screenshot_readback_texture->GetMapPointer()),
|
||||
static_cast<int>(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<u32>(scaled_rc.GetWidth()), static_cast<u32>(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<const XFBSource*>(xfb_sources[i]);
|
||||
TargetRectangle source_rect = xfb_source->sourceRc;
|
||||
TargetRectangle draw_rect;
|
||||
|
||||
int xfb_width = static_cast<int>(xfb_source->srcWidth);
|
||||
int xfb_height = static_cast<int>(xfb_source->srcHeight);
|
||||
int h_offset = (static_cast<s32>(xfb_source->srcAddr) - static_cast<s32>(xfb_addr)) /
|
||||
(static_cast<s32>(fb_stride) * 2);
|
||||
draw_rect.top =
|
||||
target_rect.top + h_offset * target_rect.GetHeight() / static_cast<s32>(fb_height);
|
||||
draw_rect.bottom =
|
||||
target_rect.top +
|
||||
(h_offset + xfb_height) * target_rect.GetHeight() / static_cast<s32>(fb_height);
|
||||
draw_rect.left = target_rect.left +
|
||||
(target_rect.GetWidth() -
|
||||
xfb_width * target_rect.GetWidth() / static_cast<s32>(fb_stride)) /
|
||||
2;
|
||||
draw_rect.right = target_rect.left +
|
||||
(target_rect.GetWidth() +
|
||||
xfb_width * target_rect.GetWidth() / static_cast<s32>(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<const XFBSource*>(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.
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<int>& 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<int>(output_width), static_cast<int>(dst_height), src_rect.left,
|
||||
src_rect.top, 0, src_rect.GetWidth(), src_rect.GetHeight(),
|
||||
static_cast<int>(src_texture->GetWidth()),
|
||||
static_cast<int>(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<int>(src_width), static_cast<int>(src_height));
|
||||
draw.SetPSSampler(0, intermediate_texture->GetView(), g_object_cache->GetPointSampler());
|
||||
draw.DrawWithoutVertexBuffer(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 4);
|
||||
draw.EndRenderPass();
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
@ -66,6 +66,14 @@ public:
|
|||
void CopyRectangleFromTexture(TCacheEntry* dst_texture, const MathUtil::Rectangle<int>& dst_rect,
|
||||
Texture2D* src_texture, const MathUtil::Rectangle<int>& 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<int>& 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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue