diff options
| author | Stenzek <stenzek@users.noreply.github.com> | 2016-11-04 23:11:10 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2016-11-04 23:11:10 +1000 |
| commit | ac2971b30e015a17c8aac937092a0dfd7c4beaf5 (patch) | |
| tree | 5bb0b8686a3b2e3b01155a000077a2226b70a26f /Source/Core/VideoBackends/Vulkan/TextureCache.cpp | |
| parent | d2fdafa1556aaa2b71017d9e2ac2dffc2e551008 (diff) | |
| parent | c880c37244d4c9d63ce3e3560383825097818d85 (diff) | |
Merge pull request #4374 from stenzek/vulkan-xfb
Vulkan: Cleanup and implement XFB support
Diffstat (limited to 'Source/Core/VideoBackends/Vulkan/TextureCache.cpp')
| -rw-r--r-- | Source/Core/VideoBackends/Vulkan/TextureCache.cpp | 429 |
1 files changed, 316 insertions, 113 deletions
diff --git a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp index 60790c687f..d99200b203 100644 --- a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp +++ b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp @@ -44,9 +44,13 @@ TextureCache::~TextureCache() TextureCache::DeleteShaders(); } -bool TextureCache::Initialize(StateTracker* state_tracker) +TextureCache* TextureCache::GetInstance() +{ + return static_cast<TextureCache*>(g_texture_cache.get()); +} + +bool TextureCache::Initialize() { - m_state_tracker = state_tracker; m_texture_upload_buffer = StreamBuffer::Create(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, INITIAL_TEXTURE_UPLOAD_BUFFER_SIZE, MAXIMUM_TEXTURE_UPLOAD_BUFFER_SIZE); @@ -99,8 +103,8 @@ void TextureCache::ConvertTexture(TCacheEntryBase* base_entry, TCacheEntryBase* if (unconverted->IsEfbCopy()) { command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); - m_state_tracker->EndRenderPass(); - m_state_tracker->SetPendingRebind(); + StateTracker::GetInstance()->EndRenderPass(); + StateTracker::GetInstance()->SetPendingRebind(); } else { @@ -109,24 +113,25 @@ void TextureCache::ConvertTexture(TCacheEntryBase* base_entry, TCacheEntryBase* } m_palette_texture_converter->ConvertTexture( - m_state_tracker, command_buffer, GetRenderPassForTextureUpdate(entry->GetTexture()), - entry->GetFramebuffer(), unconverted->GetTexture(), entry->config.width, entry->config.height, - palette, format, unconverted->format); + command_buffer, GetRenderPassForTextureUpdate(entry->GetTexture()), entry->GetFramebuffer(), + unconverted->GetTexture(), entry->config.width, entry->config.height, palette, format, + unconverted->format); // Render pass transitions to SHADER_READ_ONLY. entry->GetTexture()->OverrideImageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } +static bool IsDepthCopyFormat(PEControl::PixelFormat format) +{ + return format == PEControl::Z24; +} + void TextureCache::CopyEFB(u8* dst, u32 format, u32 native_width, u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride, PEControl::PixelFormat src_format, const EFBRectangle& src_rect, bool is_intensity, bool scale_by_half) { - // A better way of doing this would be nice. - FramebufferManager* framebuffer_mgr = - static_cast<FramebufferManager*>(g_framebuffer_manager.get()); - // Flush EFB pokes first, as they're expected to be included. - framebuffer_mgr->FlushEFBPokes(m_state_tracker); + FramebufferManager::GetInstance()->FlushEFBPokes(); // MSAA case where we need to resolve first. // TODO: Do in one pass. @@ -134,29 +139,120 @@ void TextureCache::CopyEFB(u8* dst, u32 format, u32 native_width, u32 bytes_per_ VkRect2D region = {{scaled_src_rect.left, scaled_src_rect.top}, {static_cast<u32>(scaled_src_rect.GetWidth()), static_cast<u32>(scaled_src_rect.GetHeight())}}; - Texture2D* src_texture = (src_format == PEControl::Z24) ? - framebuffer_mgr->ResolveEFBDepthTexture(m_state_tracker, region) : - framebuffer_mgr->ResolveEFBColorTexture(m_state_tracker, region); + Texture2D* src_texture; + if (IsDepthCopyFormat(src_format)) + src_texture = FramebufferManager::GetInstance()->ResolveEFBDepthTexture(region); + else + src_texture = FramebufferManager::GetInstance()->ResolveEFBColorTexture(region); // End render pass before barrier (since we have no self-dependencies) - m_state_tracker->EndRenderPass(); - m_state_tracker->SetPendingRebind(); - m_state_tracker->InvalidateDescriptorSets(); - m_state_tracker->OnReadback(); + StateTracker::GetInstance()->EndRenderPass(); + StateTracker::GetInstance()->SetPendingRebind(); + StateTracker::GetInstance()->InvalidateDescriptorSets(); + StateTracker::GetInstance()->OnReadback(); // Transition to shader resource before reading. VkImageLayout original_layout = src_texture->GetLayout(); src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - m_texture_encoder->EncodeTextureToRam(m_state_tracker, src_texture->GetView(), dst, format, - native_width, bytes_per_row, num_blocks_y, memory_stride, - src_format, is_intensity, scale_by_half, src_rect); + m_texture_encoder->EncodeTextureToRam(src_texture->GetView(), dst, format, native_width, + bytes_per_row, num_blocks_y, memory_stride, src_format, + is_intensity, scale_by_half, src_rect); // Transition back to original state src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), original_layout); } +void TextureCache::CopyRectangleFromTexture(TCacheEntry* dst_texture, + const MathUtil::Rectangle<int>& dst_rect, + Texture2D* src_texture, + const MathUtil::Rectangle<int>& src_rect) +{ + // Fast path when not scaling the image. + if (src_rect.GetWidth() == dst_rect.GetWidth() && src_rect.GetHeight() == dst_rect.GetHeight()) + CopyTextureRectangle(dst_texture, dst_rect, src_texture, src_rect); + else + ScaleTextureRectangle(dst_texture, dst_rect, src_texture, src_rect); +} + +void TextureCache::CopyTextureRectangle(TCacheEntry* dst_texture, + const MathUtil::Rectangle<int>& dst_rect, + Texture2D* src_texture, + const MathUtil::Rectangle<int>& src_rect) +{ + _assert_msg_(VIDEO, static_cast<u32>(src_rect.GetWidth()) <= src_texture->GetWidth() && + static_cast<u32>(src_rect.GetHeight()) <= src_texture->GetHeight(), + "Source rect is too large for CopyRectangleFromTexture"); + + _assert_msg_(VIDEO, static_cast<u32>(dst_rect.GetWidth()) <= dst_texture->config.width && + static_cast<u32>(dst_rect.GetHeight()) <= dst_texture->config.height, + "Dest rect is too large for CopyRectangleFromTexture"); + + VkImageCopy image_copy = { + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, + src_texture->GetLayers()}, // VkImageSubresourceLayers srcSubresource + {src_rect.left, src_rect.top, 0}, // VkOffset3D srcOffset + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, // VkImageSubresourceLayers dstSubresource + dst_texture->config.layers}, + {dst_rect.left, dst_rect.top, 0}, // VkOffset3D dstOffset + {static_cast<uint32_t>(src_rect.GetWidth()), static_cast<uint32_t>(src_rect.GetHeight()), + 1} // VkExtent3D extent + }; + + // Must be called outside of a render pass. + StateTracker::GetInstance()->EndRenderPass(); + + src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + dst_texture->GetTexture()->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + vkCmdCopyImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), src_texture->GetImage(), + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_texture->GetTexture()->GetImage(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); +} + +void TextureCache::ScaleTextureRectangle(TCacheEntry* dst_texture, + const MathUtil::Rectangle<int>& dst_rect, + Texture2D* src_texture, + const MathUtil::Rectangle<int>& src_rect) +{ + // Can't do this within a game render pass. + StateTracker::GetInstance()->EndRenderPass(); + StateTracker::GetInstance()->SetPendingRebind(); + + // Can't render to a non-rendertarget (no framebuffer). + _assert_msg_(VIDEO, dst_texture->config.rendertarget, + "Destination texture for partial copy is not a rendertarget"); + + // Render pass expects dst_texture to be in SHADER_READ_ONLY state. + src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + dst_texture->GetTexture()->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), + g_object_cache->GetStandardPipelineLayout(), + GetRenderPassForTextureUpdate(dst_texture->GetTexture()), + g_object_cache->GetPassthroughVertexShader(), + g_object_cache->GetPassthroughGeometryShader(), m_copy_shader); + + VkRect2D region = { + {dst_rect.left, dst_rect.top}, + {static_cast<u32>(dst_rect.GetWidth()), static_cast<u32>(dst_rect.GetHeight())}}; + draw.BeginRenderPass(dst_texture->GetFramebuffer(), region); + draw.SetPSSampler(0, src_texture->GetView(), g_object_cache->GetLinearSampler()); + draw.DrawQuad(dst_rect.left, dst_rect.top, dst_rect.GetWidth(), dst_rect.GetHeight(), + 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 destination texture to SHADER_READ_ONLY. + dst_texture->GetTexture()->OverrideImageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); +} + TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntryConfig& config) { // Determine image usage, we need to flag as an attachment if it can be used as a rendertarget. @@ -207,7 +303,7 @@ TextureCacheBase::TCacheEntryBase* TextureCache::CreateTexture(const TCacheEntry texture->GetLayout(), &clear_value, 1, &clear_range); } - return new TCacheEntry(config, this, std::move(texture), framebuffer); + return new TCacheEntry(config, std::move(texture), framebuffer); } bool TextureCache::CreateRenderPasses() @@ -319,20 +415,17 @@ VkRenderPass TextureCache::GetRenderPassForTextureUpdate(const Texture2D* textur return m_update_render_pass; } -TextureCache::TCacheEntry::TCacheEntry(const TCacheEntryConfig& config_, TextureCache* parent, +TextureCache::TCacheEntry::TCacheEntry(const TCacheEntryConfig& config_, std::unique_ptr<Texture2D> texture, VkFramebuffer framebuffer) - : TCacheEntryBase(config_), m_parent(parent), m_texture(std::move(texture)), - m_framebuffer(framebuffer) + : TCacheEntryBase(config_), m_texture(std::move(texture)), m_framebuffer(framebuffer) { } TextureCache::TCacheEntry::~TCacheEntry() { - // Texture is automatically cleaned up, however, we don't want to leave it bound to the state - // tracker. - m_parent->m_state_tracker->UnbindTexture(m_texture->GetView()); - + // Texture is automatically cleaned up, however, we don't want to leave it bound. + StateTracker::GetInstance()->UnbindTexture(m_texture->GetView()); if (m_framebuffer != VK_NULL_HANDLE) g_command_buffer_mgr->DeferFramebufferDestruction(m_framebuffer); } @@ -376,14 +469,14 @@ void TextureCache::TCacheEntry::Load(unsigned int width, unsigned int height, (upload_size + upload_alignment) <= MAXIMUM_TEXTURE_UPLOAD_BUFFER_SIZE) { // Assume tightly packed rows, with no padding as the buffer source. - StreamBuffer* upload_buffer = m_parent->m_texture_upload_buffer.get(); + StreamBuffer* upload_buffer = TextureCache::GetInstance()->m_texture_upload_buffer.get(); // Allocate memory from the streaming buffer for the texture data. if (!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(m_parent->m_state_tracker, false); + Util::ExecuteCurrentCommandsAndRestoreState(false); // Try allocating again. This may cause a fence wait. if (!upload_buffer->ReserveMemory(upload_size, g_vulkan_context->GetBufferImageGranularity())) @@ -467,26 +560,28 @@ void TextureCache::TCacheEntry::FromRenderTarget(u8* dst, PEControl::PixelFormat FramebufferManager* framebuffer_mgr = static_cast<FramebufferManager*>(g_framebuffer_manager.get()); TargetRectangle scaled_src_rect = g_renderer->ConvertEFBRectangle(src_rect); - bool is_depth_copy = (src_format == PEControl::Z24); + bool is_depth_copy = IsDepthCopyFormat(src_format); // Flush EFB pokes first, as they're expected to be included. - framebuffer_mgr->FlushEFBPokes(m_parent->m_state_tracker); + framebuffer_mgr->FlushEFBPokes(); // Has to be flagged as a render target. _assert_(m_framebuffer != VK_NULL_HANDLE); // Can't be done in a render pass, since we're doing our own render pass! - StateTracker* state_tracker = m_parent->m_state_tracker; VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); - state_tracker->EndRenderPass(); + StateTracker::GetInstance()->EndRenderPass(); // Transition EFB to shader resource before binding VkRect2D region = {{scaled_src_rect.left, scaled_src_rect.top}, {static_cast<u32>(scaled_src_rect.GetWidth()), static_cast<u32>(scaled_src_rect.GetHeight())}}; - Texture2D* src_texture = is_depth_copy ? - framebuffer_mgr->ResolveEFBDepthTexture(state_tracker, region) : - framebuffer_mgr->ResolveEFBColorTexture(state_tracker, region); + Texture2D* src_texture; + if (is_depth_copy) + src_texture = FramebufferManager::GetInstance()->ResolveEFBDepthTexture(region); + else + src_texture = FramebufferManager::GetInstance()->ResolveEFBColorTexture(region); + VkSampler src_sampler = scale_by_half ? g_object_cache->GetLinearSampler() : g_object_cache->GetPointSampler(); VkImageLayout original_layout = src_texture->GetLayout(); @@ -494,9 +589,10 @@ void TextureCache::TCacheEntry::FromRenderTarget(u8* dst, PEControl::PixelFormat UtilityShaderDraw draw( command_buffer, g_object_cache->GetPushConstantPipelineLayout(), - m_parent->GetRenderPassForTextureUpdate(m_texture.get()), + TextureCache::GetInstance()->GetRenderPassForTextureUpdate(m_texture.get()), g_object_cache->GetPassthroughVertexShader(), g_object_cache->GetPassthroughGeometryShader(), - is_depth_copy ? m_parent->m_efb_depth_to_tex_shader : m_parent->m_efb_color_to_tex_shader); + is_depth_copy ? TextureCache::GetInstance()->m_efb_depth_to_tex_shader : + TextureCache::GetInstance()->m_efb_color_to_tex_shader); draw.SetPushConstants(colmat, (is_depth_copy ? sizeof(float) * 20 : sizeof(float) * 28)); draw.SetPSSampler(0, src_texture->GetView(), src_sampler); @@ -512,7 +608,7 @@ void TextureCache::TCacheEntry::FromRenderTarget(u8* dst, PEControl::PixelFormat draw.EndRenderPass(); // We touched everything, so put it back. - state_tracker->SetPendingRebind(); + StateTracker::GetInstance()->SetPendingRebind(); // Transition the EFB back to its original layout. src_texture->TransitionToLayout(command_buffer, original_layout); @@ -526,78 +622,19 @@ void TextureCache::TCacheEntry::CopyRectangleFromTexture(const TCacheEntryBase* const MathUtil::Rectangle<int>& dst_rect) { const TCacheEntry* source_vk = static_cast<const TCacheEntry*>(source); - VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); - - // Fast path when not scaling the image. - if (src_rect.GetWidth() == dst_rect.GetWidth() && src_rect.GetHeight() == dst_rect.GetHeight()) - { - // These assertions should hold true unless the base code is passing us sizes too large, in - // which case it should be fixed instead. - _assert_msg_(VIDEO, static_cast<u32>(src_rect.GetWidth()) <= source->config.width && - static_cast<u32>(src_rect.GetHeight()) <= source->config.height, - "Source rect is too large for CopyRectangleFromTexture"); - - _assert_msg_(VIDEO, static_cast<u32>(dst_rect.GetWidth()) <= config.width && - static_cast<u32>(dst_rect.GetHeight()) <= config.height, - "Dest rect is too large for CopyRectangleFromTexture"); - - VkImageCopy image_copy = { - {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, - source->config.layers}, // VkImageSubresourceLayers srcSubresource - {src_rect.left, src_rect.top, 0}, // VkOffset3D srcOffset - {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, - config.layers}, // VkImageSubresourceLayers dstSubresource - {dst_rect.left, dst_rect.top, 0}, // VkOffset3D dstOffset - {static_cast<uint32_t>(src_rect.GetWidth()), static_cast<uint32_t>(src_rect.GetHeight()), - 1} // VkExtent3D extent - }; - - // Must be called outside of a render pass. - m_parent->m_state_tracker->EndRenderPass(); - - source_vk->m_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - m_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - vkCmdCopyImage(command_buffer, source_vk->m_texture->GetImage(), - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_texture->GetImage(), - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); - - m_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - source_vk->m_texture->TransitionToLayout(command_buffer, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - return; - } - - // Can't do this within a game render pass. - m_parent->m_state_tracker->EndRenderPass(); - m_parent->m_state_tracker->SetPendingRebind(); + TextureCache::GetInstance()->CopyRectangleFromTexture(this, dst_rect, source_vk->GetTexture(), + src_rect); - // Can't render to a non-rendertarget (no framebuffer). - _assert_msg_(VIDEO, config.rendertarget, - "Destination texture for partial copy is not a rendertarget"); - - UtilityShaderDraw draw( - g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetStandardPipelineLayout(), - m_parent->GetRenderPassForTextureUpdate(m_texture.get()), - g_object_cache->GetPassthroughVertexShader(), VK_NULL_HANDLE, m_parent->m_copy_shader); - - VkRect2D region = { - {dst_rect.left, dst_rect.top}, - {static_cast<u32>(dst_rect.GetWidth()), static_cast<u32>(dst_rect.GetHeight())}}; - draw.BeginRenderPass(m_framebuffer, region); - draw.SetPSSampler(0, source_vk->GetTexture()->GetView(), g_object_cache->GetLinearSampler()); - draw.DrawQuad(dst_rect.left, dst_rect.top, dst_rect.GetWidth(), dst_rect.GetHeight(), - src_rect.left, src_rect.top, 0, src_rect.GetWidth(), src_rect.GetHeight(), - source->config.width, source->config.height); - draw.EndRenderPass(); - - // Render pass transitions texture to SHADER_READ_ONLY. - m_texture->OverrideImageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + // Ensure textures are ready for use again. + m_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + source_vk->GetTexture()->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } void TextureCache::TCacheEntry::Bind(unsigned int stage) { - m_parent->m_state_tracker->SetTexture(stage, m_texture->GetView()); + StateTracker::GetInstance()->SetTexture(stage, m_texture->GetView()); } bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int level) @@ -617,7 +654,7 @@ bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int l // since we'll be executing the command buffer. m_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - m_parent->m_state_tracker->EndRenderPass(); + StateTracker::GetInstance()->EndRenderPass(); // Copy to download buffer. staging_texture->CopyFromImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), @@ -630,8 +667,8 @@ bool TextureCache::TCacheEntry::Save(const std::string& filename, unsigned int l // Block until the GPU has finished copying to the staging texture. g_command_buffer_mgr->ExecuteCommandBuffer(false, true); - m_parent->m_state_tracker->InvalidateDescriptorSets(); - m_parent->m_state_tracker->SetPendingRebind(); + StateTracker::GetInstance()->InvalidateDescriptorSets(); + StateTracker::GetInstance()->SetPendingRebind(); // Map the staging texture so we can copy the contents out. if (staging_texture->Map()) @@ -722,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; @@ -737,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() @@ -762,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 |
