Vulkan: Implement virtual/real XFB support

This commit is contained in:
Stenzek 2016-10-22 22:41:42 +10:00
parent 3593fa27ab
commit 5182e6b549
8 changed files with 456 additions and 56 deletions

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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.