Vulkan: Move XFB encoding/decoding to TextureConverter
This commit is contained in:
parent
d67463e0a7
commit
804cd0ff03
|
@ -18,6 +18,7 @@
|
||||||
#include "VideoBackends/Vulkan/StateTracker.h"
|
#include "VideoBackends/Vulkan/StateTracker.h"
|
||||||
#include "VideoBackends/Vulkan/StreamBuffer.h"
|
#include "VideoBackends/Vulkan/StreamBuffer.h"
|
||||||
#include "VideoBackends/Vulkan/Texture2D.h"
|
#include "VideoBackends/Vulkan/Texture2D.h"
|
||||||
|
#include "VideoBackends/Vulkan/TextureConverter.h"
|
||||||
#include "VideoBackends/Vulkan/Util.h"
|
#include "VideoBackends/Vulkan/Util.h"
|
||||||
#include "VideoBackends/Vulkan/VertexFormat.h"
|
#include "VideoBackends/Vulkan/VertexFormat.h"
|
||||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||||
|
@ -1411,7 +1412,7 @@ void FramebufferManager::CopyToRealXFB(u32 xfb_addr, u32 fb_stride, u32 fb_heigh
|
||||||
|
|
||||||
// The destination stride can differ from the copy region width, in which case the pixels
|
// The destination stride can differ from the copy region width, in which case the pixels
|
||||||
// outside the copy region should not be written to.
|
// outside the copy region should not be written to.
|
||||||
TextureCache::GetInstance()->EncodeYUYVTextureToMemory(
|
TextureCache::GetInstance()->GetTextureConverter()->EncodeTextureToMemoryYUYV(
|
||||||
xfb_ptr, static_cast<u32>(source_rc.GetWidth()), fb_stride, fb_height, src_texture,
|
xfb_ptr, static_cast<u32>(source_rc.GetWidth()), fb_stride, fb_height, src_texture,
|
||||||
scaled_rc);
|
scaled_rc);
|
||||||
|
|
||||||
|
@ -1437,8 +1438,8 @@ void XFBSource::DecodeToTexture(u32 xfb_addr, u32 fb_width, u32 fb_height)
|
||||||
// Guest memory -> GPU EFB Textures
|
// Guest memory -> GPU EFB Textures
|
||||||
const u8* src_ptr = Memory::GetPointer(xfb_addr);
|
const u8* src_ptr = Memory::GetPointer(xfb_addr);
|
||||||
_assert_(src_ptr);
|
_assert_(src_ptr);
|
||||||
TextureCache::GetInstance()->DecodeYUYVTextureFromMemory(m_texture.get(), src_ptr, fb_width,
|
TextureCache::GetInstance()->GetTextureConverter()->DecodeYUYVTextureFromMemory(
|
||||||
fb_width * 2, fb_height);
|
m_texture.get(), src_ptr, fb_width, fb_width * 2, fb_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void XFBSource::CopyEFB(float gamma)
|
void XFBSource::CopyEFB(float gamma)
|
||||||
|
|
|
@ -730,52 +730,6 @@ 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 header = g_object_cache->GetUtilityShaderHeader();
|
||||||
std::string source;
|
std::string source;
|
||||||
|
|
||||||
|
@ -791,14 +745,8 @@ bool TextureCache::CompileShaders()
|
||||||
source = header + EFB_DEPTH_TO_TEX_SOURCE;
|
source = header + EFB_DEPTH_TO_TEX_SOURCE;
|
||||||
m_efb_depth_to_tex_shader = Util::CompileAndCreateFragmentShader(source);
|
m_efb_depth_to_tex_shader = Util::CompileAndCreateFragmentShader(source);
|
||||||
|
|
||||||
source = header + RGB_TO_YUYV_SHADER_SOURCE;
|
return m_copy_shader != VK_NULL_HANDLE && m_efb_color_to_tex_shader != VK_NULL_HANDLE &&
|
||||||
m_rgb_to_yuyv_shader = Util::CompileAndCreateFragmentShader(source);
|
m_efb_depth_to_tex_shader != VK_NULL_HANDLE;
|
||||||
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_rgb_to_yuyv_shader != VK_NULL_HANDLE &&
|
|
||||||
m_yuyv_to_rgb_shader != VK_NULL_HANDLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::DeleteShaders()
|
void TextureCache::DeleteShaders()
|
||||||
|
@ -822,120 +770,6 @@ void TextureCache::DeleteShaders()
|
||||||
vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_efb_depth_to_tex_shader, nullptr);
|
vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_efb_depth_to_tex_shader, nullptr);
|
||||||
m_efb_depth_to_tex_shader = VK_NULL_HANDLE;
|
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_converter->GetEncodingTexture();
|
|
||||||
StagingTexture2D* download_texture = m_texture_converter->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->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD),
|
|
||||||
m_texture_converter->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_converter->GetEncodingTextureFramebuffer(), region);
|
|
||||||
draw.SetPSSampler(0, src_texture->GetView(), g_object_cache->GetLinearSampler());
|
|
||||||
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_converter->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->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD),
|
|
||||||
m_texture_converter->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
|
} // namespace Vulkan
|
||||||
|
|
|
@ -48,6 +48,8 @@ public:
|
||||||
|
|
||||||
static TextureCache* GetInstance();
|
static TextureCache* GetInstance();
|
||||||
|
|
||||||
|
StreamBuffer* GetUploadBuffer() const { return m_texture_upload_buffer.get(); }
|
||||||
|
TextureConverter* GetTextureConverter() const { return m_texture_converter.get(); }
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
|
||||||
bool CompileShaders() override;
|
bool CompileShaders() override;
|
||||||
|
@ -65,14 +67,6 @@ public:
|
||||||
void CopyRectangleFromTexture(TCacheEntry* dst_texture, const MathUtil::Rectangle<int>& dst_rect,
|
void CopyRectangleFromTexture(TCacheEntry* dst_texture, const MathUtil::Rectangle<int>& dst_rect,
|
||||||
Texture2D* src_texture, const MathUtil::Rectangle<int>& src_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:
|
private:
|
||||||
bool CreateRenderPasses();
|
bool CreateRenderPasses();
|
||||||
VkRenderPass GetRenderPassForTextureUpdate(const Texture2D* texture) const;
|
VkRenderPass GetRenderPassForTextureUpdate(const Texture2D* texture) const;
|
||||||
|
@ -95,8 +89,6 @@ private:
|
||||||
VkShaderModule m_copy_shader = VK_NULL_HANDLE;
|
VkShaderModule m_copy_shader = VK_NULL_HANDLE;
|
||||||
VkShaderModule m_efb_color_to_tex_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_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
|
} // namespace Vulkan
|
||||||
|
|
|
@ -56,6 +56,11 @@ TextureConverter::~TextureConverter()
|
||||||
if (shader != VK_NULL_HANDLE)
|
if (shader != VK_NULL_HANDLE)
|
||||||
vkDestroyShaderModule(g_vulkan_context->GetDevice(), shader, nullptr);
|
vkDestroyShaderModule(g_vulkan_context->GetDevice(), shader, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_rgb_to_yuyv_shader != VK_NULL_HANDLE)
|
||||||
|
vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_rgb_to_yuyv_shader, nullptr);
|
||||||
|
if (m_yuyv_to_rgb_shader != VK_NULL_HANDLE)
|
||||||
|
vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_yuyv_to_rgb_shader, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextureConverter::Initialize()
|
bool TextureConverter::Initialize()
|
||||||
|
@ -96,6 +101,12 @@ bool TextureConverter::Initialize()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!CompileYUYVConversionShaders())
|
||||||
|
{
|
||||||
|
PanicAlert("Failed to compile YUYV conversion shaders");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,6 +239,112 @@ void TextureConverter::EncodeTextureToMemory(VkImageView src_texture, u8* dest_p
|
||||||
memory_stride);
|
memory_stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextureConverter::EncodeTextureToMemoryYUYV(void* dst_ptr, u32 dst_width, u32 dst_stride,
|
||||||
|
u32 dst_height, Texture2D* src_texture,
|
||||||
|
const MathUtil::Rectangle<int>& src_rect)
|
||||||
|
{
|
||||||
|
StateTracker::GetInstance()->EndRenderPass();
|
||||||
|
|
||||||
|
// Borrow framebuffer from EFB2RAM encoder.
|
||||||
|
VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer();
|
||||||
|
src_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
m_encoding_render_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->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD),
|
||||||
|
m_encoding_render_pass, g_object_cache->GetPassthroughVertexShader(),
|
||||||
|
VK_NULL_HANDLE, m_rgb_to_yuyv_shader);
|
||||||
|
VkRect2D region = {{0, 0}, {output_width, dst_height}};
|
||||||
|
draw.BeginRenderPass(m_encoding_render_framebuffer, region);
|
||||||
|
draw.SetPSSampler(0, src_texture->GetView(), g_object_cache->GetLinearSampler());
|
||||||
|
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.
|
||||||
|
m_encoding_render_texture->OverrideImageLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||||
|
|
||||||
|
// Copy from encoding texture to download buffer.
|
||||||
|
m_encoding_download_texture->CopyFromImage(command_buffer, m_encoding_render_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.
|
||||||
|
m_encoding_download_texture->ReadTexels(0, 0, output_width, dst_height, dst_ptr, dst_stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureConverter::DecodeYUYVTextureFromMemory(TextureCache::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();
|
||||||
|
StateTracker::GetInstance()->SetPendingRebind();
|
||||||
|
|
||||||
|
// We share the upload buffer with normal textures here, since the XFB buffers aren't very large.
|
||||||
|
u32 upload_size = src_stride * src_height;
|
||||||
|
StreamBuffer* texture_upload_buffer = TextureCache::GetInstance()->GetUploadBuffer();
|
||||||
|
if (!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 (!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 = texture_upload_buffer->GetCurrentOffset();
|
||||||
|
std::memcpy(texture_upload_buffer->GetCurrentHostPointer(), src_ptr, upload_size);
|
||||||
|
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();
|
||||||
|
m_encoding_render_texture->TransitionToLayout(command_buffer,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||||
|
vkCmdCopyBufferToImage(command_buffer, texture_upload_buffer->GetBuffer(),
|
||||||
|
m_encoding_render_texture->GetImage(),
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
|
||||||
|
m_encoding_render_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->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD),
|
||||||
|
m_encoding_render_pass, 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, m_encoding_render_texture->GetView(), g_object_cache->GetPointSampler());
|
||||||
|
draw.DrawWithoutVertexBuffer(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 4);
|
||||||
|
draw.EndRenderPass();
|
||||||
|
}
|
||||||
|
|
||||||
bool TextureConverter::CreateTexelBuffer()
|
bool TextureConverter::CreateTexelBuffer()
|
||||||
{
|
{
|
||||||
// Prefer an 8MB buffer if possible, but use less if the device doesn't support this.
|
// Prefer an 8MB buffer if possible, but use less if the device doesn't support this.
|
||||||
|
@ -472,4 +589,61 @@ bool TextureConverter::CreateEncodingDownloadTexture()
|
||||||
return m_encoding_download_texture && m_encoding_download_texture->Map();
|
return m_encoding_download_texture && m_encoding_download_texture->Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TextureConverter::CompileYUYVConversionShaders()
|
||||||
|
{
|
||||||
|
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 = 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_rgb_to_yuyv_shader != VK_NULL_HANDLE && m_yuyv_to_rgb_shader != VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|
|
@ -25,10 +25,6 @@ public:
|
||||||
TextureConverter();
|
TextureConverter();
|
||||||
~TextureConverter();
|
~TextureConverter();
|
||||||
|
|
||||||
VkRenderPass GetEncodingRenderPass() const { return m_encoding_render_pass; }
|
|
||||||
Texture2D* GetEncodingTexture() const { return m_encoding_render_texture.get(); }
|
|
||||||
VkFramebuffer GetEncodingTextureFramebuffer() const { return m_encoding_render_framebuffer; }
|
|
||||||
StagingTexture2D* GetDownloadTexture() const { return m_encoding_download_texture.get(); }
|
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
|
||||||
// Applies palette to dst_entry, using indices from src_entry.
|
// Applies palette to dst_entry, using indices from src_entry.
|
||||||
|
@ -42,6 +38,14 @@ public:
|
||||||
PEControl::PixelFormat src_format, bool is_intensity,
|
PEControl::PixelFormat src_format, bool is_intensity,
|
||||||
int scale_by_half, const EFBRectangle& source);
|
int scale_by_half, const EFBRectangle& source);
|
||||||
|
|
||||||
|
// Encodes texture to guest memory in XFB (YUYV) format.
|
||||||
|
void EncodeTextureToMemoryYUYV(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(TextureCache::TCacheEntry* dst_texture, const void* src_ptr,
|
||||||
|
u32 src_width, u32 src_stride, u32 src_height);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const u32 NUM_TEXTURE_ENCODING_SHADERS = 64;
|
static const u32 NUM_TEXTURE_ENCODING_SHADERS = 64;
|
||||||
static const u32 ENCODING_TEXTURE_WIDTH = EFB_WIDTH * 4;
|
static const u32 ENCODING_TEXTURE_WIDTH = EFB_WIDTH * 4;
|
||||||
|
@ -59,6 +63,8 @@ private:
|
||||||
bool CreateEncodingTexture();
|
bool CreateEncodingTexture();
|
||||||
bool CreateEncodingDownloadTexture();
|
bool CreateEncodingDownloadTexture();
|
||||||
|
|
||||||
|
bool CompileYUYVConversionShaders();
|
||||||
|
|
||||||
// Shared between conversion types
|
// Shared between conversion types
|
||||||
std::unique_ptr<StreamBuffer> m_texel_buffer;
|
std::unique_ptr<StreamBuffer> m_texel_buffer;
|
||||||
VkBufferView m_texel_buffer_view_r16_uint = VK_NULL_HANDLE;
|
VkBufferView m_texel_buffer_view_r16_uint = VK_NULL_HANDLE;
|
||||||
|
@ -73,6 +79,10 @@ private:
|
||||||
std::unique_ptr<Texture2D> m_encoding_render_texture;
|
std::unique_ptr<Texture2D> m_encoding_render_texture;
|
||||||
VkFramebuffer m_encoding_render_framebuffer = VK_NULL_HANDLE;
|
VkFramebuffer m_encoding_render_framebuffer = VK_NULL_HANDLE;
|
||||||
std::unique_ptr<StagingTexture2D> m_encoding_download_texture;
|
std::unique_ptr<StagingTexture2D> m_encoding_download_texture;
|
||||||
|
|
||||||
|
// XFB encoding/decoding shaders
|
||||||
|
VkShaderModule m_rgb_to_yuyv_shader = VK_NULL_HANDLE;
|
||||||
|
VkShaderModule m_yuyv_to_rgb_shader = VK_NULL_HANDLE;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|
Loading…
Reference in New Issue