diff --git a/Source/Core/VideoBackends/Vulkan/BoundingBox.cpp b/Source/Core/VideoBackends/Vulkan/BoundingBox.cpp index e9dad9f9d6..5cfc174bf8 100644 --- a/Source/Core/VideoBackends/Vulkan/BoundingBox.cpp +++ b/Source/Core/VideoBackends/Vulkan/BoundingBox.cpp @@ -10,6 +10,7 @@ #include "VideoBackends/Vulkan/BoundingBox.h" #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" +#include "VideoBackends/Vulkan/Renderer.h" #include "VideoBackends/Vulkan/StagingBuffer.h" #include "VideoBackends/Vulkan/StateTracker.h" #include "VideoBackends/Vulkan/Util.h" @@ -47,7 +48,7 @@ bool BoundingBox::Initialize() return true; } -void BoundingBox::Flush(StateTracker* state_tracker) +void BoundingBox::Flush() { if (m_gpu_buffer == VK_NULL_HANDLE) return; @@ -75,7 +76,7 @@ void BoundingBox::Flush(StateTracker* state_tracker) // However, the writes must be serialized, so we can't put it in the init buffer. if (!updated_buffer) { - state_tracker->EndRenderPass(); + StateTracker::GetInstance()->EndRenderPass(); // Ensure GPU buffer is in a state where it can be transferred to. Util::BufferMemoryBarrier( @@ -104,7 +105,7 @@ void BoundingBox::Flush(StateTracker* state_tracker) m_valid = true; } -void BoundingBox::Invalidate(StateTracker* state_tracker) +void BoundingBox::Invalidate() { if (m_gpu_buffer == VK_NULL_HANDLE) return; @@ -112,19 +113,19 @@ void BoundingBox::Invalidate(StateTracker* state_tracker) m_valid = false; } -s32 BoundingBox::Get(StateTracker* state_tracker, size_t index) +s32 BoundingBox::Get(size_t index) { _assert_(index < NUM_VALUES); if (!m_valid) - Readback(state_tracker); + Readback(); s32 value; m_readback_buffer->Read(index * sizeof(s32), &value, sizeof(value), false); return value; } -void BoundingBox::Set(StateTracker* state_tracker, size_t index, s32 value) +void BoundingBox::Set(size_t index, s32 value) { _assert_(index < NUM_VALUES); @@ -212,10 +213,10 @@ bool BoundingBox::CreateReadbackBuffer() return true; } -void BoundingBox::Readback(StateTracker* state_tracker) +void BoundingBox::Readback() { // Can't be done within a render pass. - state_tracker->EndRenderPass(); + StateTracker::GetInstance()->EndRenderPass(); // Ensure all writes are completed to the GPU buffer prior to the transfer. Util::BufferMemoryBarrier( @@ -240,7 +241,7 @@ void BoundingBox::Readback(StateTracker* state_tracker) VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); // Wait until these commands complete. - Util::ExecuteCurrentCommandsAndRestoreState(state_tracker, false, true); + Util::ExecuteCurrentCommandsAndRestoreState(false, true); // Cache is now valid. m_readback_buffer->InvalidateCPUCache(); diff --git a/Source/Core/VideoBackends/Vulkan/BoundingBox.h b/Source/Core/VideoBackends/Vulkan/BoundingBox.h index 0647c08607..872f49aa31 100644 --- a/Source/Core/VideoBackends/Vulkan/BoundingBox.h +++ b/Source/Core/VideoBackends/Vulkan/BoundingBox.h @@ -15,7 +15,6 @@ namespace Vulkan { class StagingBuffer; -class StateTracker; class BoundingBox { @@ -28,16 +27,16 @@ public: VkBuffer GetGPUBuffer() const { return m_gpu_buffer; } VkDeviceSize GetGPUBufferOffset() const { return 0; } VkDeviceSize GetGPUBufferSize() const { return BUFFER_SIZE; } - s32 Get(StateTracker* state_tracker, size_t index); - void Set(StateTracker* state_tracker, size_t index, s32 value); + s32 Get(size_t index); + void Set(size_t index, s32 value); - void Invalidate(StateTracker* state_tracker); - void Flush(StateTracker* state_tracker); + void Invalidate(); + void Flush(); private: bool CreateGPUBuffer(); bool CreateReadbackBuffer(); - void Readback(StateTracker* state_tracker); + void Readback(); VkBuffer m_gpu_buffer = VK_NULL_HANDLE; VkDeviceMemory m_gpu_memory = VK_NULL_HANDLE; diff --git a/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp b/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp index f78cf9953f..c3fc47cb5e 100644 --- a/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp @@ -10,6 +10,8 @@ #include "Common/CommonFuncs.h" #include "Common/Logging/Log.h" +#include "Core/HW/Memmap.h" + #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/StagingTexture2D.h" @@ -49,6 +51,11 @@ FramebufferManager::~FramebufferManager() DestroyPokeShaders(); } +FramebufferManager* FramebufferManager::GetInstance() +{ + return static_cast(g_framebuffer_manager.get()); +} + bool FramebufferManager::Initialize() { if (!CreateEFBRenderPass()) @@ -450,15 +457,14 @@ void FramebufferManager::ReinterpretPixelData(int convtype) std::swap(m_efb_framebuffer, m_efb_convert_framebuffer); } -Texture2D* FramebufferManager::ResolveEFBColorTexture(StateTracker* state_tracker, - const VkRect2D& region) +Texture2D* FramebufferManager::ResolveEFBColorTexture(const VkRect2D& region) { // Return the normal EFB texture if multisampling is off. if (m_efb_samples == VK_SAMPLE_COUNT_1_BIT) return m_efb_color_texture.get(); // Can't resolve within a render pass. - state_tracker->EndRenderPass(); + StateTracker::GetInstance()->EndRenderPass(); // Resolving is considered to be a transfer operation. m_efb_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), @@ -485,15 +491,14 @@ Texture2D* FramebufferManager::ResolveEFBColorTexture(StateTracker* state_tracke return m_efb_resolve_color_texture.get(); } -Texture2D* FramebufferManager::ResolveEFBDepthTexture(StateTracker* state_tracker, - const VkRect2D& region) +Texture2D* FramebufferManager::ResolveEFBDepthTexture(const VkRect2D& region) { // Return the normal EFB texture if multisampling is off. if (m_efb_samples == VK_SAMPLE_COUNT_1_BIT) return m_efb_depth_texture.get(); // Can't resolve within a render pass. - state_tracker->EndRenderPass(); + StateTracker::GetInstance()->EndRenderPass(); m_efb_depth_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); @@ -659,9 +664,9 @@ void FramebufferManager::DestroyConversionShaders() DestroyShader(m_ps_depth_resolve); } -u32 FramebufferManager::PeekEFBColor(StateTracker* state_tracker, u32 x, u32 y) +u32 FramebufferManager::PeekEFBColor(u32 x, u32 y) { - if (!m_color_readback_texture_valid && !PopulateColorReadbackTexture(state_tracker)) + if (!m_color_readback_texture_valid && !PopulateColorReadbackTexture()) return 0; u32 value; @@ -669,18 +674,18 @@ u32 FramebufferManager::PeekEFBColor(StateTracker* state_tracker, u32 x, u32 y) return value; } -bool FramebufferManager::PopulateColorReadbackTexture(StateTracker* state_tracker) +bool FramebufferManager::PopulateColorReadbackTexture() { // Can't be in our normal render pass. - state_tracker->EndRenderPass(); - state_tracker->OnReadback(); + StateTracker::GetInstance()->EndRenderPass(); + StateTracker::GetInstance()->OnReadback(); // Issue a copy from framebuffer -> copy texture if we have >1xIR or MSAA on. VkRect2D src_region = {{0, 0}, {m_efb_width, m_efb_height}}; Texture2D* src_texture = m_efb_color_texture.get(); VkImageAspectFlags src_aspect = VK_IMAGE_ASPECT_COLOR_BIT; if (m_efb_samples > 1) - src_texture = ResolveEFBColorTexture(state_tracker, src_region); + src_texture = ResolveEFBColorTexture(src_region); if (m_efb_width != EFB_WIDTH || m_efb_height != EFB_HEIGHT) { @@ -728,8 +733,8 @@ bool FramebufferManager::PopulateColorReadbackTexture(StateTracker* state_tracke // Wait until the copy is complete. g_command_buffer_mgr->ExecuteCommandBuffer(false, true); - state_tracker->InvalidateDescriptorSets(); - state_tracker->SetPendingRebind(); + StateTracker::GetInstance()->InvalidateDescriptorSets(); + StateTracker::GetInstance()->SetPendingRebind(); // Map to host memory. if (!m_color_readback_texture->IsMapped() && !m_color_readback_texture->Map()) @@ -739,9 +744,9 @@ bool FramebufferManager::PopulateColorReadbackTexture(StateTracker* state_tracke return true; } -float FramebufferManager::PeekEFBDepth(StateTracker* state_tracker, u32 x, u32 y) +float FramebufferManager::PeekEFBDepth(u32 x, u32 y) { - if (!m_depth_readback_texture_valid && !PopulateDepthReadbackTexture(state_tracker)) + if (!m_depth_readback_texture_valid && !PopulateDepthReadbackTexture()) return 0.0f; float value; @@ -749,11 +754,11 @@ float FramebufferManager::PeekEFBDepth(StateTracker* state_tracker, u32 x, u32 y return value; } -bool FramebufferManager::PopulateDepthReadbackTexture(StateTracker* state_tracker) +bool FramebufferManager::PopulateDepthReadbackTexture() { // Can't be in our normal render pass. - state_tracker->EndRenderPass(); - state_tracker->OnReadback(); + StateTracker::GetInstance()->EndRenderPass(); + StateTracker::GetInstance()->OnReadback(); // Issue a copy from framebuffer -> copy texture if we have >1xIR or MSAA on. VkRect2D src_region = {{0, 0}, {m_efb_width, m_efb_height}}; @@ -762,7 +767,7 @@ bool FramebufferManager::PopulateDepthReadbackTexture(StateTracker* state_tracke if (m_efb_samples > 1) { // EFB depth resolves are written out as color textures - src_texture = ResolveEFBDepthTexture(state_tracker, src_region); + src_texture = ResolveEFBDepthTexture(src_region); src_aspect = VK_IMAGE_ASPECT_COLOR_BIT; } if (m_efb_width != EFB_WIDTH || m_efb_height != EFB_HEIGHT) @@ -812,8 +817,8 @@ bool FramebufferManager::PopulateDepthReadbackTexture(StateTracker* state_tracke // Wait until the copy is complete. g_command_buffer_mgr->ExecuteCommandBuffer(false, true); - state_tracker->InvalidateDescriptorSets(); - state_tracker->SetPendingRebind(); + StateTracker::GetInstance()->InvalidateDescriptorSets(); + StateTracker::GetInstance()->SetPendingRebind(); // Map to host memory. if (!m_depth_readback_texture->IsMapped() && !m_depth_readback_texture->Map()) @@ -1086,11 +1091,11 @@ void FramebufferManager::DestroyReadbackFramebuffer() } } -void FramebufferManager::PokeEFBColor(StateTracker* state_tracker, u32 x, u32 y, u32 color) +void FramebufferManager::PokeEFBColor(u32 x, u32 y, u32 color) { // Flush if we exceeded the number of vertices per batch. if ((m_color_poke_vertices.size() + 6) > MAX_POKE_VERTICES) - FlushEFBPokes(state_tracker); + FlushEFBPokes(); CreatePokeVertices(&m_color_poke_vertices, x, y, 0.0f, color); @@ -1099,11 +1104,11 @@ void FramebufferManager::PokeEFBColor(StateTracker* state_tracker, u32 x, u32 y, m_color_readback_texture->WriteTexel(x, y, &color, sizeof(color)); } -void FramebufferManager::PokeEFBDepth(StateTracker* state_tracker, u32 x, u32 y, float depth) +void FramebufferManager::PokeEFBDepth(u32 x, u32 y, float depth) { // Flush if we exceeded the number of vertices per batch. if ((m_color_poke_vertices.size() + 6) > MAX_POKE_VERTICES) - FlushEFBPokes(state_tracker); + FlushEFBPokes(); CreatePokeVertices(&m_depth_poke_vertices, x, y, depth, 0); @@ -1140,27 +1145,22 @@ void FramebufferManager::CreatePokeVertices(std::vector* destinat } } -void FramebufferManager::FlushEFBPokes(StateTracker* state_tracker) +void FramebufferManager::FlushEFBPokes() { if (!m_color_poke_vertices.empty()) { - DrawPokeVertices(state_tracker, m_color_poke_vertices.data(), m_color_poke_vertices.size(), - true, false); - + DrawPokeVertices(m_color_poke_vertices.data(), m_color_poke_vertices.size(), true, false); m_color_poke_vertices.clear(); } if (!m_depth_poke_vertices.empty()) { - DrawPokeVertices(state_tracker, m_depth_poke_vertices.data(), m_depth_poke_vertices.size(), - false, true); - + DrawPokeVertices(m_depth_poke_vertices.data(), m_depth_poke_vertices.size(), false, true); m_depth_poke_vertices.clear(); } } -void FramebufferManager::DrawPokeVertices(StateTracker* state_tracker, - const EFBPokeVertex* vertices, size_t vertex_count, +void FramebufferManager::DrawPokeVertices(const EFBPokeVertex* vertices, size_t vertex_count, bool write_color, bool write_depth) { // Relatively simple since we don't have any bindings. @@ -1205,7 +1205,7 @@ void FramebufferManager::DrawPokeVertices(StateTracker* state_tracker, { // Kick a command buffer first. WARN_LOG(VIDEO, "Kicking command buffer due to no EFB poke space."); - Util::ExecuteCurrentCommandsAndRestoreState(state_tracker, true); + Util::ExecuteCurrentCommandsAndRestoreState(true); command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); if (!m_poke_vertex_stream_buffer->ReserveMemory(vertices_size, sizeof(EfbPokeData), true, true, @@ -1221,9 +1221,9 @@ void FramebufferManager::DrawPokeVertices(StateTracker* state_tracker, m_poke_vertex_stream_buffer->CommitMemory(vertices_size); // Set up state. - state_tracker->EndClearRenderPass(); - state_tracker->BeginRenderPass(); - state_tracker->SetPendingRebind(); + StateTracker::GetInstance()->EndClearRenderPass(); + StateTracker::GetInstance()->BeginRenderPass(); + StateTracker::GetInstance()->SetPendingRebind(); Util::SetViewportAndScissor(command_buffer, 0, 0, m_efb_width, m_efb_height); vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdBindVertexBuffers(command_buffer, 0, 1, &vb_buffer, &vb_offset); @@ -1367,4 +1367,96 @@ void FramebufferManager::DestroyPokeShaders() } } +std::unique_ptr FramebufferManager::CreateXFBSource(unsigned int target_width, + unsigned int target_height, + unsigned int layers) +{ + TextureCacheBase::TCacheEntryConfig config; + config.width = target_width; + config.height = target_height; + config.layers = layers; + config.rendertarget = true; + auto* base_texture = TextureCache::GetInstance()->CreateTexture(config); + auto* texture = static_cast(base_texture); + if (!texture) + { + PanicAlert("Failed to create texture for XFB source"); + return nullptr; + } + + return std::make_unique(std::unique_ptr(texture)); +} + +void FramebufferManager::CopyToRealXFB(u32 xfb_addr, u32 fb_stride, u32 fb_height, + const EFBRectangle& source_rc, float gamma) +{ + // Pending/batched EFB pokes should be included in the copied image. + FlushEFBPokes(); + + // Schedule early command-buffer execution. + StateTracker::GetInstance()->EndRenderPass(); + StateTracker::GetInstance()->OnReadback(); + + // GPU EFB textures -> Guest memory + u8* xfb_ptr = Memory::GetPointer(xfb_addr); + _assert_(xfb_ptr); + + // source_rc is in native coordinates, so scale it to the internal resolution. + TargetRectangle scaled_rc = g_renderer->ConvertEFBRectangle(source_rc); + VkRect2D scaled_rc_vk = { + {scaled_rc.left, scaled_rc.top}, + {static_cast(scaled_rc.GetWidth()), static_cast(scaled_rc.GetHeight())}}; + Texture2D* src_texture = ResolveEFBColorTexture(scaled_rc_vk); + + // 2 bytes per pixel, so divide fb_stride by 2 to get the width. + TextureCache::GetInstance()->EncodeYUYVTextureToMemory(xfb_ptr, fb_stride / 2, fb_stride, + fb_height, src_texture, scaled_rc); + + // If we sourced directly from the EFB framebuffer, restore it to a color attachment. + if (src_texture == m_efb_color_texture.get()) + { + src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + } +} + +XFBSource::XFBSource(std::unique_ptr texture) + : XFBSourceBase(), m_texture(std::move(texture)) +{ +} + +XFBSource::~XFBSource() +{ +} + +void XFBSource::DecodeToTexture(u32 xfb_addr, u32 fb_width, u32 fb_height) +{ + // Guest memory -> GPU EFB Textures + const u8* src_ptr = Memory::GetPointer(xfb_addr); + _assert_(src_ptr); + TextureCache::GetInstance()->DecodeYUYVTextureFromMemory(m_texture.get(), src_ptr, fb_width, + fb_width * 2, fb_height); +} + +void XFBSource::CopyEFB(float gamma) +{ + // Pending/batched EFB pokes should be included in the copied image. + FramebufferManager::GetInstance()->FlushEFBPokes(); + + // Virtual XFB, copy EFB at native resolution to m_texture + MathUtil::Rectangle rect(0, 0, static_cast(texWidth), static_cast(texHeight)); + VkRect2D vk_rect = {{rect.left, rect.top}, + {static_cast(rect.GetWidth()), static_cast(rect.GetHeight())}}; + + Texture2D* src_texture = FramebufferManager::GetInstance()->ResolveEFBColorTexture(vk_rect); + TextureCache::GetInstance()->CopyRectangleFromTexture(m_texture.get(), rect, src_texture, rect); + + // If we sourced directly from the EFB framebuffer, restore it to a color attachment. + if (src_texture == FramebufferManager::GetInstance()->GetEFBColorTexture()) + { + src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + } +} + } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/FramebufferManager.h b/Source/Core/VideoBackends/Vulkan/FramebufferManager.h index a8c5f0fc64..92a689fb41 100644 --- a/Source/Core/VideoBackends/Vulkan/FramebufferManager.h +++ b/Source/Core/VideoBackends/Vulkan/FramebufferManager.h @@ -8,6 +8,7 @@ #include "Common/CommonTypes.h" #include "VideoBackends/Vulkan/Constants.h" +#include "VideoBackends/Vulkan/TextureCache.h" #include "VideoCommon/FramebufferManagerBase.h" namespace Vulkan @@ -17,12 +18,7 @@ class StateTracker; class StreamBuffer; class Texture2D; class VertexFormat; - -class XFBSource : public XFBSourceBase -{ - void DecodeToTexture(u32 xfb_addr, u32 fb_width, u32 fb_height) override {} - void CopyEFB(float gamma) override {} -}; +class XFBSource; class FramebufferManager : public FramebufferManagerBase { @@ -30,6 +26,8 @@ public: FramebufferManager(); ~FramebufferManager(); + static FramebufferManager* GetInstance(); + bool Initialize(); VkRenderPass GetEFBLoadRenderPass() const { return m_efb_load_render_pass; } @@ -45,15 +43,11 @@ public: std::unique_ptr CreateXFBSource(unsigned int target_width, unsigned int target_height, - unsigned int layers) override - { - return std::make_unique(); - } + unsigned int layers) override; + // GPU EFB textures -> Guest void CopyToRealXFB(u32 xfb_addr, u32 fb_stride, u32 fb_height, const EFBRectangle& source_rc, - float gamma = 1.0f) override - { - } + float gamma = 1.0f) override; void ResizeEFBTextures(); @@ -69,18 +63,18 @@ public: // This render pass can be used for other readback operations. VkRenderPass GetColorCopyForReadbackRenderPass() const { return m_copy_color_render_pass; } // Resolve color/depth textures to a non-msaa texture, and return it. - Texture2D* ResolveEFBColorTexture(StateTracker* state_tracker, const VkRect2D& region); - Texture2D* ResolveEFBDepthTexture(StateTracker* state_tracker, const VkRect2D& region); + Texture2D* ResolveEFBColorTexture(const VkRect2D& region); + Texture2D* ResolveEFBDepthTexture(const VkRect2D& region); // Reads a framebuffer value back from the GPU. This may block if the cache is not current. - u32 PeekEFBColor(StateTracker* state_tracker, u32 x, u32 y); - float PeekEFBDepth(StateTracker* state_tracker, u32 x, u32 y); + u32 PeekEFBColor(u32 x, u32 y); + float PeekEFBDepth(u32 x, u32 y); void InvalidatePeekCache(); // Writes a value to the framebuffer. This will never block, and writes will be batched. - void PokeEFBColor(StateTracker* state_tracker, u32 x, u32 y, u32 color); - void PokeEFBDepth(StateTracker* state_tracker, u32 x, u32 y, float depth); - void FlushEFBPokes(StateTracker* state_tracker); + void PokeEFBColor(u32 x, u32 y, u32 color); + void PokeEFBDepth(u32 x, u32 y, float depth); + void FlushEFBPokes(); private: struct EFBPokeVertex @@ -112,14 +106,14 @@ private: bool CompilePokeShaders(); void DestroyPokeShaders(); - bool PopulateColorReadbackTexture(StateTracker* state_tracker); - bool PopulateDepthReadbackTexture(StateTracker* state_tracker); + bool PopulateColorReadbackTexture(); + bool PopulateDepthReadbackTexture(); void CreatePokeVertices(std::vector* destination_list, u32 x, u32 y, float z, u32 color); - void DrawPokeVertices(StateTracker* state_tracker, const EFBPokeVertex* vertices, - size_t vertex_count, bool write_color, bool write_depth); + void DrawPokeVertices(const EFBPokeVertex* vertices, size_t vertex_count, bool write_color, + bool write_depth); VkRenderPass m_efb_load_render_pass = VK_NULL_HANDLE; VkRenderPass m_efb_clear_render_pass = VK_NULL_HANDLE; @@ -173,4 +167,24 @@ private: VkShaderModule m_poke_fragment_shader = VK_NULL_HANDLE; }; +// The XFB source class simply wraps a texture cache entry. +// All the required functionality is provided by TextureCache. +class XFBSource final : public XFBSourceBase +{ +public: + explicit XFBSource(std::unique_ptr texture); + ~XFBSource(); + + TextureCache::TCacheEntry* GetTexture() const { return m_texture.get(); } + // Guest -> GPU EFB Textures + void DecodeToTexture(u32 xfb_addr, u32 fb_width, u32 fb_height) override; + + // Used for virtual XFB + void CopyEFB(float gamma) override; + +private: + std::unique_ptr m_texture; + VkFramebuffer m_framebuffer; +}; + } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp index e5e65c1a73..61e685874a 100644 --- a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp +++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp @@ -899,12 +899,12 @@ bool operator<(const SamplerState& lhs, const SamplerState& rhs) bool ObjectCache::CompileSharedShaders() { static const char PASSTHROUGH_VERTEX_SHADER_SOURCE[] = R"( - layout(location = 0) in float4 ipos; - layout(location = 5) in float4 icol0; - layout(location = 8) in float3 itex0; + layout(location = 0) in vec4 ipos; + layout(location = 5) in vec4 icol0; + layout(location = 8) in vec3 itex0; - layout(location = 0) out float3 uv0; - layout(location = 1) out float4 col0; + layout(location = 0) out vec3 uv0; + layout(location = 1) out vec4 col0; void main() { @@ -918,17 +918,11 @@ bool ObjectCache::CompileSharedShaders() layout(triangles) in; layout(triangle_strip, max_vertices = EFB_LAYERS * 3) out; - in VertexData - { - float3 uv0; - float4 col0; - } in_data[]; + layout(location = 0) in vec3 in_uv0[]; + layout(location = 1) in vec4 in_col0[]; - out VertexData - { - float3 uv0; - float4 col0; - } out_data; + layout(location = 0) out vec3 out_uv0; + layout(location = 1) out vec4 out_col0; void main() { @@ -938,8 +932,8 @@ bool ObjectCache::CompileSharedShaders() { gl_Layer = j; gl_Position = gl_in[i].gl_Position; - out_data.uv0 = float3(in_data[i].uv0.xy, float(j)); - out_data.col0 = in_data[i].col0; + out_uv0 = vec3(in_uv0[i].xy, float(j)); + out_col0 = in_col0[i]; EmitVertex(); } EndPrimitive(); @@ -948,7 +942,7 @@ bool ObjectCache::CompileSharedShaders() )"; static const char SCREEN_QUAD_VERTEX_SHADER_SOURCE[] = R"( - layout(location = 0) out float3 uv0; + layout(location = 0) out vec3 uv0; void main() { @@ -959,9 +953,9 @@ bool ObjectCache::CompileSharedShaders() * 2 0,2 0,1 -1,1 BL * 3 1,2 1,1 1,1 BR */ - vec2 rawpos = float2(float(gl_VertexID & 1), clamp(float(gl_VertexID & 2), 0.0f, 1.0f)); - gl_Position = float4(rawpos * 2.0f - 1.0f, 0.0f, 1.0f); - uv0 = float3(rawpos, 0.0f); + vec2 rawpos = vec2(float(gl_VertexID & 1), clamp(float(gl_VertexID & 2), 0.0f, 1.0f)); + gl_Position = vec4(rawpos * 2.0f - 1.0f, 0.0f, 1.0f); + uv0 = vec3(rawpos, 0.0f); } )"; @@ -969,15 +963,9 @@ bool ObjectCache::CompileSharedShaders() layout(triangles) in; layout(triangle_strip, max_vertices = EFB_LAYERS * 3) out; - in VertexData - { - float3 uv0; - } in_data[]; + layout(location = 0) in vec3 in_uv0[]; - out VertexData - { - float3 uv0; - } out_data; + layout(location = 0) out vec3 out_uv0; void main() { @@ -987,7 +975,7 @@ bool ObjectCache::CompileSharedShaders() { gl_Layer = j; gl_Position = gl_in[i].gl_Position; - out_data.uv0 = float3(in_data[i].uv0.xy, float(j)); + out_uv0 = vec3(in_uv0[i].xy, float(j)); EmitVertex(); } EndPrimitive(); diff --git a/Source/Core/VideoBackends/Vulkan/PaletteTextureConverter.cpp b/Source/Core/VideoBackends/Vulkan/PaletteTextureConverter.cpp index 07100eb7bb..3e992d4944 100644 --- a/Source/Core/VideoBackends/Vulkan/PaletteTextureConverter.cpp +++ b/Source/Core/VideoBackends/Vulkan/PaletteTextureConverter.cpp @@ -62,8 +62,7 @@ bool PaletteTextureConverter::Initialize() return true; } -void PaletteTextureConverter::ConvertTexture(StateTracker* state_tracker, - VkCommandBuffer command_buffer, +void PaletteTextureConverter::ConvertTexture(VkCommandBuffer command_buffer, VkRenderPass render_pass, VkFramebuffer dst_framebuffer, Texture2D* src_texture, u32 width, u32 height, void* palette, @@ -89,7 +88,7 @@ void PaletteTextureConverter::ConvertTexture(StateTracker* state_tracker, g_command_buffer_mgr->AllocateDescriptorSet(m_palette_set_layout)) == VK_NULL_HANDLE) { WARN_LOG(VIDEO, "Executing command list while waiting for space in palette buffer"); - Util::ExecuteCurrentCommandsAndRestoreState(state_tracker, false); + Util::ExecuteCurrentCommandsAndRestoreState(false); if (!m_palette_stream_buffer->ReserveMemory(palette_size, g_vulkan_context->GetTexelBufferAlignment()) || diff --git a/Source/Core/VideoBackends/Vulkan/PaletteTextureConverter.h b/Source/Core/VideoBackends/Vulkan/PaletteTextureConverter.h index 39b8cccd8c..2540b5affa 100644 --- a/Source/Core/VideoBackends/Vulkan/PaletteTextureConverter.h +++ b/Source/Core/VideoBackends/Vulkan/PaletteTextureConverter.h @@ -13,7 +13,6 @@ namespace Vulkan { -class StateTracker; class Texture2D; // Since this converter uses a uniform texel buffer, we can't use the general pipeline generators. @@ -26,10 +25,9 @@ public: bool Initialize(); - void ConvertTexture(StateTracker* state_tracker, VkCommandBuffer command_buffer, - VkRenderPass render_pass, VkFramebuffer dst_framebuffer, - Texture2D* src_texture, u32 width, u32 height, void* palette, - TlutFormat format, u32 src_format); + void ConvertTexture(VkCommandBuffer command_buffer, VkRenderPass render_pass, + VkFramebuffer dst_framebuffer, Texture2D* src_texture, u32 width, u32 height, + void* palette, TlutFormat format, u32 src_format); private: static const size_t NUM_PALETTE_CONVERSION_SHADERS = 3; diff --git a/Source/Core/VideoBackends/Vulkan/PerfQuery.cpp b/Source/Core/VideoBackends/Vulkan/PerfQuery.cpp index 2a64babb32..11ba898bf6 100644 --- a/Source/Core/VideoBackends/Vulkan/PerfQuery.cpp +++ b/Source/Core/VideoBackends/Vulkan/PerfQuery.cpp @@ -32,10 +32,13 @@ PerfQuery::~PerfQuery() vkDestroyQueryPool(g_vulkan_context->GetDevice(), m_query_pool, nullptr); } -bool PerfQuery::Initialize(StateTracker* state_tracker) +Vulkan::PerfQuery* PerfQuery::GetInstance() { - m_state_tracker = state_tracker; + return static_cast(g_perf_query.get()); +} +bool PerfQuery::Initialize() +{ if (!CreateQueryPool()) { PanicAlert("Failed to create query pool"); @@ -86,11 +89,11 @@ void PerfQuery::EnableQuery(PerfQueryGroup type) // Ensure the query starts within a render pass. // TODO: Is this needed? - m_state_tracker->BeginRenderPass(); + StateTracker::GetInstance()->BeginRenderPass(); vkCmdBeginQuery(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, index, flags); // Prevent background command buffer submission while the query is active. - m_state_tracker->SetBackgroundCommandBufferExecution(false); + StateTracker::GetInstance()->SetBackgroundCommandBufferExecution(false); } } @@ -101,7 +104,7 @@ void PerfQuery::DisableQuery(PerfQueryGroup type) // DisableQuery should be called for each EnableQuery, so subtract one to get the previous one. u32 index = (m_query_read_pos + m_query_count - 1) % PERF_QUERY_BUFFER_SIZE; vkCmdEndQuery(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, index); - m_state_tracker->SetBackgroundCommandBufferExecution(true); + StateTracker::GetInstance()->SetBackgroundCommandBufferExecution(true); DEBUG_LOG(VIDEO, "end query %u", index); } } @@ -113,7 +116,7 @@ void PerfQuery::ResetQuery() std::fill_n(m_results, ArraySize(m_results), 0); // Reset entire query pool, ensuring all queries are ready to write to. - m_state_tracker->EndRenderPass(); + StateTracker::GetInstance()->EndRenderPass(); vkCmdResetQueryPool(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, 0, PERF_QUERY_BUFFER_SIZE); @@ -346,7 +349,7 @@ void PerfQuery::NonBlockingPartialFlush() // Submit a command buffer in the background if the front query is not bound to one. // Ideally this will complete before the buffer fills. if (m_query_buffer[m_query_read_pos].pending_fence == VK_NULL_HANDLE) - Util::ExecuteCurrentCommandsAndRestoreState(m_state_tracker, true, false); + Util::ExecuteCurrentCommandsAndRestoreState(true, false); } void PerfQuery::BlockingPartialFlush() @@ -360,7 +363,7 @@ void PerfQuery::BlockingPartialFlush() { // This will callback OnCommandBufferQueued which will set the fence on the entry. // We wait for completion, which will also call OnCommandBufferExecuted, and clear the fence. - Util::ExecuteCurrentCommandsAndRestoreState(m_state_tracker, false, true); + Util::ExecuteCurrentCommandsAndRestoreState(false, true); } else { diff --git a/Source/Core/VideoBackends/Vulkan/PerfQuery.h b/Source/Core/VideoBackends/Vulkan/PerfQuery.h index 3d385299f1..c5f5d13e90 100644 --- a/Source/Core/VideoBackends/Vulkan/PerfQuery.h +++ b/Source/Core/VideoBackends/Vulkan/PerfQuery.h @@ -14,7 +14,6 @@ namespace Vulkan { class StagingBuffer; -class StateTracker; class PerfQuery : public PerfQueryBase { @@ -22,7 +21,9 @@ public: PerfQuery(); ~PerfQuery(); - bool Initialize(StateTracker* state_tracker); + static PerfQuery* GetInstance(); + + bool Initialize(); void EnableQuery(PerfQueryGroup type) override; void DisableQuery(PerfQueryGroup type) override; @@ -52,8 +53,6 @@ private: void NonBlockingPartialFlush(); void BlockingPartialFlush(); - StateTracker* m_state_tracker = nullptr; - // when testing in SMS: 64 was too small, 128 was ok // TODO: This should be size_t, but the base class uses u32s using PerfQueryDataType = u32; diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp index 3fac85e340..066f3f512a 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp @@ -13,6 +13,7 @@ #include "Common/MsgHandler.h" #include "Core/ConfigManager.h" +#include "Core/Core.h" #include "VideoBackends/Vulkan/BoundingBox.h" #include "VideoBackends/Vulkan/CommandBufferManager.h" @@ -63,15 +64,18 @@ Renderer::~Renderer() { g_Config.bRunning = false; UpdateActiveConfig(); - DestroyScreenshotResources(); + DestroyFrameDumpResources(); DestroyShaders(); DestroySemaphores(); } -bool Renderer::Initialize(FramebufferManager* framebuffer_mgr) +Renderer* Renderer::GetInstance() +{ + return static_cast(g_renderer.get()); +} + +bool Renderer::Initialize() { - m_framebuffer_mgr = framebuffer_mgr; - m_state_tracker = std::make_unique(); BindEFBToStateTracker(); if (!CreateSemaphores()) @@ -103,9 +107,9 @@ bool Renderer::Initialize(FramebufferManager* framebuffer_mgr) if (g_vulkan_context->SupportsBoundingBox()) { // Bind bounding box to state tracker - m_state_tracker->SetBBoxBuffer(m_bounding_box->GetGPUBuffer(), - m_bounding_box->GetGPUBufferOffset(), - m_bounding_box->GetGPUBufferSize()); + StateTracker::GetInstance()->SetBBoxBuffer(m_bounding_box->GetGPUBuffer(), + m_bounding_box->GetGPUBufferOffset(), + m_bounding_box->GetGPUBufferSize()); } // Various initialization routines will have executed commands on the command buffer. @@ -170,7 +174,7 @@ u32 Renderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) { if (type == PEEK_COLOR) { - u32 color = m_framebuffer_mgr->PeekEFBColor(m_state_tracker.get(), x, y); + u32 color = FramebufferManager::GetInstance()->PeekEFBColor(x, y); // a little-endian value is expected to be returned color = ((color & 0xFF00FF00) | ((color >> 16) & 0xFF) | ((color << 16) & 0xFF0000)); @@ -207,7 +211,7 @@ u32 Renderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) else // if (type == PEEK_Z) { // Depth buffer is inverted for improved precision near far plane - float depth = 1.0f - m_framebuffer_mgr->PeekEFBDepth(m_state_tracker.get(), x, y); + float depth = 1.0f - FramebufferManager::GetInstance()->PeekEFBDepth(x, y); u32 ret = 0; if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16) @@ -235,7 +239,7 @@ void Renderer::PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num const EfbPokeData& point = points[i]; u32 color = ((point.data & 0xFF00FF00) | ((point.data >> 16) & 0xFF) | ((point.data << 16) & 0xFF0000)); - m_framebuffer_mgr->PokeEFBColor(m_state_tracker.get(), point.x, point.y, color); + FramebufferManager::GetInstance()->PokeEFBColor(point.x, point.y, color); } } else // if (type == POKE_Z) @@ -245,14 +249,14 @@ void Renderer::PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num // Convert to floating-point depth. const EfbPokeData& point = points[i]; float depth = (1.0f - float(point.data & 0xFFFFFF) / 16777216.0f); - m_framebuffer_mgr->PokeEFBDepth(m_state_tracker.get(), point.x, point.y, depth); + FramebufferManager::GetInstance()->PokeEFBDepth(point.x, point.y, depth); } } } u16 Renderer::BBoxRead(int index) { - s32 value = m_bounding_box->Get(m_state_tracker.get(), static_cast(index)); + s32 value = m_bounding_box->Get(static_cast(index)); // Here we get the min/max value of the truncated position of the upscaled framebuffer. // So we have to correct them to the unscaled EFB sizes. @@ -294,7 +298,7 @@ void Renderer::BBoxWrite(int index, u16 value) scaled_value = scaled_value * s_target_height / EFB_HEIGHT; } - m_bounding_box->Set(m_state_tracker.get(), static_cast(index), scaled_value); + m_bounding_box->Set(static_cast(index), scaled_value); } TargetRectangle Renderer::ConvertEFBRectangle(const EFBRectangle& rc) @@ -314,8 +318,8 @@ void Renderer::BeginFrame() // Ensure that the state tracker rebinds everything, and allocates a new set // of descriptors out of the next pool. - m_state_tracker->InvalidateDescriptorSets(); - m_state_tracker->SetPendingRebind(); + StateTracker::GetInstance()->InvalidateDescriptorSets(); + StateTracker::GetInstance()->SetPendingRebind(); } void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha_enable, @@ -327,15 +331,6 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha {target_rc.left, target_rc.top}, {static_cast(target_rc.GetWidth()), static_cast(target_rc.GetHeight())}}; - // Convert RGBA8 -> floating-point values. - VkClearValue clear_color_value = {}; - VkClearValue clear_depth_value = {}; - clear_color_value.color.float32[0] = static_cast((color >> 16) & 0xFF) / 255.0f; - clear_color_value.color.float32[1] = static_cast((color >> 8) & 0xFF) / 255.0f; - clear_color_value.color.float32[2] = static_cast((color >> 0) & 0xFF) / 255.0f; - clear_color_value.color.float32[3] = static_cast((color >> 24) & 0xFF) / 255.0f; - clear_depth_value.depthStencil.depth = (1.0f - (static_cast(z & 0xFFFFFF) / 16777216.0f)); - // Determine whether the EFB has an alpha channel. If it doesn't, we can clear the alpha // channel to 0xFF. This hopefully allows us to use the fast path in most cases. if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16 || @@ -347,10 +342,19 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha color |= 0xFF000000; } + // Convert RGBA8 -> floating-point values. + VkClearValue clear_color_value = {}; + VkClearValue clear_depth_value = {}; + clear_color_value.color.float32[0] = static_cast((color >> 16) & 0xFF) / 255.0f; + clear_color_value.color.float32[1] = static_cast((color >> 8) & 0xFF) / 255.0f; + clear_color_value.color.float32[2] = static_cast((color >> 0) & 0xFF) / 255.0f; + clear_color_value.color.float32[3] = static_cast((color >> 24) & 0xFF) / 255.0f; + clear_depth_value.depthStencil.depth = (1.0f - (static_cast(z & 0xFFFFFF) / 16777216.0f)); + // If we're not in a render pass (start of the frame), we can use a clear render pass // to discard the data, rather than loading and then clearing. bool use_clear_render_pass = (color_enable && alpha_enable && z_enable); - if (m_state_tracker->InRenderPass()) + if (StateTracker::GetInstance()->InRenderPass()) { // Prefer not to end a render pass just to do a clear. use_clear_render_pass = false; @@ -360,7 +364,7 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha if (use_clear_render_pass) { VkClearValue clear_values[2] = {clear_color_value, clear_depth_value}; - m_state_tracker->BeginClearRenderPass(target_vk_rc, clear_values); + StateTracker::GetInstance()->BeginClearRenderPass(target_vk_rc, clear_values); return; } @@ -388,17 +392,17 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha } if (num_clear_attachments > 0) { - VkClearRect clear_rect = {target_vk_rc, 0, m_framebuffer_mgr->GetEFBLayers()}; - if (!m_state_tracker->IsWithinRenderArea(target_vk_rc.offset.x, target_vk_rc.offset.y, - target_vk_rc.extent.width, - target_vk_rc.extent.height)) + VkClearRect vk_rect = {target_vk_rc, 0, FramebufferManager::GetInstance()->GetEFBLayers()}; + if (!StateTracker::GetInstance()->IsWithinRenderArea( + target_vk_rc.offset.x, target_vk_rc.offset.y, target_vk_rc.extent.width, + target_vk_rc.extent.height)) { - m_state_tracker->EndClearRenderPass(); + StateTracker::GetInstance()->EndClearRenderPass(); } - m_state_tracker->BeginRenderPass(); + StateTracker::GetInstance()->BeginRenderPass(); vkCmdClearAttachments(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_clear_attachments, - clear_attachments, 1, &clear_rect); + clear_attachments, 1, &vk_rect); } } @@ -407,13 +411,14 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha return; // Clearing must occur within a render pass. - if (!m_state_tracker->IsWithinRenderArea(target_vk_rc.offset.x, target_vk_rc.offset.y, - target_vk_rc.extent.width, target_vk_rc.extent.height)) + if (!StateTracker::GetInstance()->IsWithinRenderArea(target_vk_rc.offset.x, target_vk_rc.offset.y, + target_vk_rc.extent.width, + target_vk_rc.extent.height)) { - m_state_tracker->EndClearRenderPass(); + StateTracker::GetInstance()->EndClearRenderPass(); } - m_state_tracker->BeginRenderPass(); - m_state_tracker->SetPendingRebind(); + StateTracker::GetInstance()->BeginRenderPass(); + StateTracker::GetInstance()->SetPendingRebind(); // Mask away the appropriate colors and use a shader BlendState blend_state = Util::GetNoBlendingBlendState(); @@ -431,13 +436,14 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha RasterizationState rs_state = Util::GetNoCullRasterizationState(); rs_state.per_sample_shading = g_ActiveConfig.bSSAA ? VK_TRUE : VK_FALSE; - rs_state.samples = m_framebuffer_mgr->GetEFBSamples(); + rs_state.samples = FramebufferManager::GetInstance()->GetEFBSamples(); // No need to start a new render pass, but we do need to restore viewport state - UtilityShaderDraw draw( - g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetStandardPipelineLayout(), - m_framebuffer_mgr->GetEFBLoadRenderPass(), g_object_cache->GetPassthroughVertexShader(), - g_object_cache->GetPassthroughGeometryShader(), m_clear_fragment_shader); + UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), + g_object_cache->GetStandardPipelineLayout(), + FramebufferManager::GetInstance()->GetEFBLoadRenderPass(), + g_object_cache->GetPassthroughVertexShader(), + g_object_cache->GetPassthroughGeometryShader(), m_clear_fragment_shader); draw.SetRasterizationState(rs_state); draw.SetDepthStencilState(depth_state); @@ -451,9 +457,9 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha void Renderer::ReinterpretPixelData(unsigned int convtype) { - m_state_tracker->EndRenderPass(); - m_state_tracker->SetPendingRebind(); - m_framebuffer_mgr->ReinterpretPixelData(convtype); + StateTracker::GetInstance()->EndRenderPass(); + StateTracker::GetInstance()->SetPendingRebind(); + FramebufferManager::GetInstance()->ReinterpretPixelData(convtype); // EFB framebuffer has now changed, so update accordingly. BindEFBToStateTracker(); @@ -462,37 +468,38 @@ 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. - m_framebuffer_mgr->FlushEFBPokes(m_state_tracker.get()); + // Pending/batched EFB pokes should be included in the final image. + FramebufferManager::GetInstance()->FlushEFBPokes(); - // End the current render pass. - m_state_tracker->EndRenderPass(); - m_state_tracker->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}, - {m_framebuffer_mgr->GetEFBWidth(), m_framebuffer_mgr->GetEFBHeight()}}; - Texture2D* efb_color_texture = - m_framebuffer_mgr->ResolveEFBColorTexture(m_state_tracker.get(), 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)) + // Check that we actually have an image to render in XFB-on modes. + if ((!XFBWrited && !g_ActiveConfig.RealXFBEnabled()) || !fb_width || !fb_height) { - DumpFrameData(reinterpret_cast(m_screenshot_readback_texture->GetMapPointer()), - static_cast(m_screenshot_render_texture->GetWidth()), - static_cast(m_screenshot_render_texture->GetHeight()), - static_cast(m_screenshot_readback_texture->GetRowStride()), ticks); - FinishFrameData(); + 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; } - // Restore the EFB color texture to color attachment ready for rendering the next frame. - m_framebuffer_mgr->GetEFBColorTexture()->TransitionToLayout( - g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + // End the current render pass. + StateTracker::GetInstance()->EndRenderPass(); + StateTracker::GetInstance()->OnEndFrame(); + + // Draw to the screenshot buffer if needed. + // We don't actually copy it to the frame dump here, instead we submit the present to the screen + // and the readback in one command buffer, wait for it, and then dump the frame. This allows us + // to render to the screen and dump the frame in one command buffer instead of two. + VkFence frame_dump_fence = g_command_buffer_mgr->GetCurrentCommandBufferFence(); + bool dump_this_frame = IsFrameDumping() && DrawFrameDump(rc, xfb_addr, xfb_sources, xfb_count, + fb_width, fb_stride, fb_height); + + // If we're dumping frames, don't bother waking the worker thread, since we have to wait anyway. + bool submit_on_background_thread = !dump_this_frame; // 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 @@ -502,20 +509,30 @@ 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 // the available semaphore to be signaled before executing the buffer. This final submission // can happen off-thread in the background while we're preparing the next frame. g_command_buffer_mgr->SubmitCommandBuffer( - true, m_image_available_semaphore, m_rendering_finished_semaphore, + submit_on_background_thread, m_image_available_semaphore, m_rendering_finished_semaphore, m_swap_chain->GetSwapChain(), m_swap_chain->GetCurrentImageIndex()); } else { // No swap chain, just execute command buffer. - g_command_buffer_mgr->SubmitCommandBuffer(true); + g_command_buffer_mgr->SubmitCommandBuffer(submit_on_background_thread); + } + + // If we're dumping frames, wait for the GPU to complete these commands, and then copy the image. + // NOTE: This call must come immediately after submitting the command buffer. Placing any other + // function calls between the submit and wait could cause another command buffer to be submitted, + // making frame_dump_fence refer to an incorrect fence. + if (dump_this_frame) + { + g_command_buffer_mgr->WaitForFence(frame_dump_fence); + DumpFrame(ticks); } // NOTE: It is important that no rendering calls are made to the EFB between submitting the @@ -542,7 +559,97 @@ void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height TextureCacheBase::Cleanup(frameCount); } -void Renderer::DrawScreen(const TargetRectangle& src_rect, const Texture2D* src_tex) +void Renderer::DrawFrame(VkRenderPass render_pass, const EFBRectangle& rc, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height) +{ + if (!g_ActiveConfig.bUseXFB) + DrawEFB(render_pass, rc); + else if (!g_ActiveConfig.bUseRealXFB) + DrawVirtualXFB(render_pass, xfb_addr, xfb_sources, xfb_count, fb_width, fb_stride, fb_height); + else + DrawRealXFB(render_pass, xfb_sources, xfb_count, fb_width, fb_stride, fb_height); +} + +void Renderer::DrawEFB(VkRenderPass render_pass, const EFBRectangle& rc) +{ + // Scale the source rectangle to the selected internal resolution. + TargetRectangle scaled_rc = Renderer::ConvertEFBRectangle(rc); + scaled_rc.left = std::max(scaled_rc.left, 0); + scaled_rc.right = std::max(scaled_rc.right, 0); + scaled_rc.top = std::max(scaled_rc.top, 0); + scaled_rc.bottom = std::max(scaled_rc.bottom, 0); + + // Transition the EFB render target to a shader resource. + VkRect2D src_region = { + {0, 0}, {static_cast(scaled_rc.GetWidth()), static_cast(scaled_rc.GetHeight())}}; + Texture2D* efb_color_texture = + FramebufferManager::GetInstance()->ResolveEFBColorTexture(src_region); + efb_color_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + // Copy EFB -> backbuffer + BlitScreen(render_pass, GetTargetRectangle(), scaled_rc, efb_color_texture, true); + + // Restore the EFB color texture to color attachment ready for rendering the next frame. + if (efb_color_texture == FramebufferManager::GetInstance()->GetEFBColorTexture()) + { + FramebufferManager::GetInstance()->GetEFBColorTexture()->TransitionToLayout( + g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + } +} + +void Renderer::DrawVirtualXFB(VkRenderPass render_pass, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height) +{ + const TargetRectangle& target_rect = GetTargetRectangle(); + for (u32 i = 0; i < xfb_count; ++i) + { + const XFBSource* xfb_source = static_cast(xfb_sources[i]); + TargetRectangle source_rect = xfb_source->sourceRc; + TargetRectangle draw_rect; + + int xfb_width = static_cast(xfb_source->srcWidth); + int xfb_height = static_cast(xfb_source->srcHeight); + int h_offset = (static_cast(xfb_source->srcAddr) - static_cast(xfb_addr)) / + (static_cast(fb_stride) * 2); + draw_rect.top = + target_rect.top + h_offset * target_rect.GetHeight() / static_cast(fb_height); + draw_rect.bottom = + target_rect.top + + (h_offset + xfb_height) * target_rect.GetHeight() / static_cast(fb_height); + draw_rect.left = target_rect.left + + (target_rect.GetWidth() - + xfb_width * target_rect.GetWidth() / static_cast(fb_stride)) / + 2; + draw_rect.right = target_rect.left + + (target_rect.GetWidth() + + xfb_width * target_rect.GetWidth() / static_cast(fb_stride)) / + 2; + + source_rect.right -= Renderer::EFBToScaledX(fb_stride - fb_width); + BlitScreen(render_pass, draw_rect, source_rect, xfb_source->GetTexture()->GetTexture(), true); + } +} + +void Renderer::DrawRealXFB(VkRenderPass render_pass, const XFBSourceBase* const* xfb_sources, + u32 xfb_count, u32 fb_width, u32 fb_stride, u32 fb_height) +{ + const TargetRectangle& target_rect = GetTargetRectangle(); + for (u32 i = 0; i < xfb_count; ++i) + { + const XFBSource* xfb_source = static_cast(xfb_sources[i]); + TargetRectangle source_rect = xfb_source->sourceRc; + TargetRectangle draw_rect = target_rect; + source_rect.right -= fb_stride - fb_width; + BlitScreen(render_pass, draw_rect, source_rect, xfb_source->GetTexture()->GetTexture(), true); + } +} + +void Renderer::DrawScreen(const EFBRectangle& rc, u32 xfb_addr, + const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, + u32 fb_stride, u32 fb_height) { // Grab the next image from the swap chain in preparation for drawing the window. VkResult res = m_swap_chain->AcquireNextImage(m_image_available_semaphore); @@ -563,32 +670,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. @@ -596,7 +702,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::DrawFrameDump(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. @@ -607,37 +715,45 @@ bool Renderer::DrawScreenshot(const TargetRectangle& src_rect, const Texture2D* target_rect.top = 0; u32 width = std::max(1u, static_cast(target_rect.GetWidth())); u32 height = std::max(1u, static_cast(target_rect.GetHeight())); - if (!ResizeScreenshotBuffer(width, height)) + if (!ResizeFrameDumpBuffer(width, height)) return false; VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; VkClearRect clear_rect = {{{0, 0}, {width, height}}, 0, 1}; VkClearAttachment clear_attachment = {VK_IMAGE_ASPECT_COLOR_BIT, 0, clear_value}; - VkRenderPassBeginInfo info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, - nullptr, - m_framebuffer_mgr->GetColorCopyForReadbackRenderPass(), - m_screenshot_framebuffer, - {{0, 0}, {width, height}}, - 1, - &clear_value}; + VkRenderPassBeginInfo info = { + VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + nullptr, + FramebufferManager::GetInstance()->GetColorCopyForReadbackRenderPass(), + m_frame_dump_framebuffer, + {{0, 0}, {width, height}}, + 1, + &clear_value}; vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &info, VK_SUBPASS_CONTENTS_INLINE); vkCmdClearAttachments(g_command_buffer_mgr->GetCurrentCommandBuffer(), 1, &clear_attachment, 1, &clear_rect); - BlitScreen(m_framebuffer_mgr->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. - m_screenshot_readback_texture->CopyFromImage( - g_command_buffer_mgr->GetCurrentCommandBuffer(), m_screenshot_render_texture->GetImage(), + m_frame_dump_readback_texture->CopyFromImage( + g_command_buffer_mgr->GetCurrentCommandBuffer(), m_frame_dump_render_texture->GetImage(), VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, width, height, 0, 0); - // Wait for the command buffer to complete. - g_command_buffer_mgr->ExecuteCommandBuffer(false, true); return true; } +void Renderer::DumpFrame(u64 ticks) +{ + DumpFrameData(reinterpret_cast(m_frame_dump_readback_texture->GetMapPointer()), + static_cast(m_frame_dump_render_texture->GetWidth()), + static_cast(m_frame_dump_render_texture->GetHeight()), + static_cast(m_frame_dump_readback_texture->GetRowStride()), ticks); + FinishFrameData(); +} + void Renderer::BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_rect, const TargetRectangle& src_rect, const Texture2D* src_tex, bool linear_filter) @@ -658,10 +774,7 @@ void Renderer::BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_r { TargetRectangle left_rect; TargetRectangle right_rect; - if (g_ActiveConfig.iStereoMode == STEREO_TAB) - ConvertStereoRectangle(dst_rect, right_rect, left_rect); - else - ConvertStereoRectangle(dst_rect, left_rect, right_rect); + ConvertStereoRectangle(dst_rect, left_rect, right_rect); draw.DrawQuad(left_rect.left, left_rect.top, left_rect.GetWidth(), left_rect.GetHeight(), src_rect.left, src_rect.top, 0, src_rect.GetWidth(), src_rect.GetHeight(), @@ -679,40 +792,40 @@ void Renderer::BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_r } } -bool Renderer::ResizeScreenshotBuffer(u32 new_width, u32 new_height) +bool Renderer::ResizeFrameDumpBuffer(u32 new_width, u32 new_height) { - if (m_screenshot_render_texture && m_screenshot_render_texture->GetWidth() == new_width && - m_screenshot_render_texture->GetHeight() == new_height) + if (m_frame_dump_render_texture && m_frame_dump_render_texture->GetWidth() == new_width && + m_frame_dump_render_texture->GetHeight() == new_height) { return true; } - if (m_screenshot_framebuffer != VK_NULL_HANDLE) + if (m_frame_dump_framebuffer != VK_NULL_HANDLE) { - vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_screenshot_framebuffer, nullptr); - m_screenshot_framebuffer = VK_NULL_HANDLE; + vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_frame_dump_framebuffer, nullptr); + m_frame_dump_framebuffer = VK_NULL_HANDLE; } - m_screenshot_render_texture = + m_frame_dump_render_texture = Texture2D::Create(new_width, new_height, 1, 1, EFB_COLOR_TEXTURE_FORMAT, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); - m_screenshot_readback_texture = StagingTexture2D::Create(STAGING_BUFFER_TYPE_READBACK, new_width, + m_frame_dump_readback_texture = StagingTexture2D::Create(STAGING_BUFFER_TYPE_READBACK, new_width, new_height, EFB_COLOR_TEXTURE_FORMAT); - if (!m_screenshot_render_texture || !m_screenshot_readback_texture || - !m_screenshot_readback_texture->Map()) + if (!m_frame_dump_render_texture || !m_frame_dump_readback_texture || + !m_frame_dump_readback_texture->Map()) { WARN_LOG(VIDEO, "Failed to resize screenshot render texture"); - m_screenshot_render_texture.reset(); - m_screenshot_readback_texture.reset(); + m_frame_dump_render_texture.reset(); + m_frame_dump_readback_texture.reset(); return false; } - VkImageView attachment = m_screenshot_render_texture->GetView(); + VkImageView attachment = m_frame_dump_render_texture->GetView(); VkFramebufferCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - info.renderPass = m_framebuffer_mgr->GetColorCopyForReadbackRenderPass(); + info.renderPass = FramebufferManager::GetInstance()->GetColorCopyForReadbackRenderPass(); info.attachmentCount = 1; info.pAttachments = &attachment; info.width = new_width; @@ -720,32 +833,32 @@ bool Renderer::ResizeScreenshotBuffer(u32 new_width, u32 new_height) info.layers = 1; VkResult res = - vkCreateFramebuffer(g_vulkan_context->GetDevice(), &info, nullptr, &m_screenshot_framebuffer); + vkCreateFramebuffer(g_vulkan_context->GetDevice(), &info, nullptr, &m_frame_dump_framebuffer); if (res != VK_SUCCESS) { WARN_LOG(VIDEO, "Failed to resize screenshot framebuffer"); - m_screenshot_render_texture.reset(); - m_screenshot_readback_texture.reset(); + m_frame_dump_render_texture.reset(); + m_frame_dump_readback_texture.reset(); return false; } // Render pass expects texture is in transfer src to start with. - m_screenshot_render_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + m_frame_dump_render_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); return true; } -void Renderer::DestroyScreenshotResources() +void Renderer::DestroyFrameDumpResources() { - if (m_screenshot_framebuffer != VK_NULL_HANDLE) + if (m_frame_dump_framebuffer != VK_NULL_HANDLE) { - vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_screenshot_framebuffer, nullptr); - m_screenshot_framebuffer = VK_NULL_HANDLE; + vkDestroyFramebuffer(g_vulkan_context->GetDevice(), m_frame_dump_framebuffer, nullptr); + m_frame_dump_framebuffer = VK_NULL_HANDLE; } - m_screenshot_render_texture.reset(); - m_screenshot_readback_texture.reset(); + m_frame_dump_render_texture.reset(); + m_frame_dump_readback_texture.reset(); } void Renderer::CheckForTargetResize(u32 fb_width, u32 fb_stride, u32 fb_height) @@ -889,8 +1002,8 @@ void Renderer::CheckForConfigChanges() if (msaa_changed || stereo_changed) { g_command_buffer_mgr->WaitForGPUIdle(); - m_framebuffer_mgr->RecreateRenderPass(); - m_framebuffer_mgr->ResizeEFBTextures(); + FramebufferManager::GetInstance()->RecreateRenderPass(); + FramebufferManager::GetInstance()->ResizeEFBTextures(); BindEFBToStateTracker(); } @@ -900,8 +1013,9 @@ void Renderer::CheckForConfigChanges() { g_command_buffer_mgr->WaitForGPUIdle(); RecompileShaders(); - m_framebuffer_mgr->RecompileShaders(); + FramebufferManager::GetInstance()->RecompileShaders(); g_object_cache->ClearPipelineCache(); + g_object_cache->RecompileSharedShaders(); } // For vsync, we need to change the present mode, which means recreating the swap chain. @@ -931,25 +1045,28 @@ void Renderer::OnSwapChainResized() void Renderer::BindEFBToStateTracker() { // Update framebuffer in state tracker - VkRect2D framebuffer_size = { - {0, 0}, {m_framebuffer_mgr->GetEFBWidth(), m_framebuffer_mgr->GetEFBHeight()}}; - m_state_tracker->SetRenderPass(m_framebuffer_mgr->GetEFBLoadRenderPass(), - m_framebuffer_mgr->GetEFBClearRenderPass()); - m_state_tracker->SetFramebuffer(m_framebuffer_mgr->GetEFBFramebuffer(), framebuffer_size); + VkRect2D framebuffer_size = {{0, 0}, + {FramebufferManager::GetInstance()->GetEFBWidth(), + FramebufferManager::GetInstance()->GetEFBHeight()}}; + StateTracker::GetInstance()->SetRenderPass( + FramebufferManager::GetInstance()->GetEFBLoadRenderPass(), + FramebufferManager::GetInstance()->GetEFBClearRenderPass()); + StateTracker::GetInstance()->SetFramebuffer( + FramebufferManager::GetInstance()->GetEFBFramebuffer(), framebuffer_size); // Update rasterization state with MSAA info RasterizationState rs_state = {}; - rs_state.bits = m_state_tracker->GetRasterizationState().bits; - rs_state.samples = m_framebuffer_mgr->GetEFBSamples(); + rs_state.bits = StateTracker::GetInstance()->GetRasterizationState().bits; + rs_state.samples = FramebufferManager::GetInstance()->GetEFBSamples(); rs_state.per_sample_shading = g_ActiveConfig.bSSAA ? VK_TRUE : VK_FALSE; - m_state_tracker->SetRasterizationState(rs_state); + StateTracker::GetInstance()->SetRasterizationState(rs_state); } void Renderer::ResizeEFBTextures() { // Ensure the GPU is finished with the current EFB textures. g_command_buffer_mgr->WaitForGPUIdle(); - m_framebuffer_mgr->ResizeEFBTextures(); + FramebufferManager::GetInstance()->ResizeEFBTextures(); BindEFBToStateTracker(); // Viewport and scissor rect have to be reset since they will be scaled differently. @@ -976,19 +1093,19 @@ void Renderer::ApplyState(bool bUseDstAlpha) void Renderer::ResetAPIState() { // End the EFB render pass if active - m_state_tracker->EndRenderPass(); + StateTracker::GetInstance()->EndRenderPass(); } void Renderer::RestoreAPIState() { // Instruct the state tracker to re-bind everything before the next draw - m_state_tracker->SetPendingRebind(); + StateTracker::GetInstance()->SetPendingRebind(); } void Renderer::SetGenerationMode() { RasterizationState new_rs_state = {}; - new_rs_state.bits = m_state_tracker->GetRasterizationState().bits; + new_rs_state.bits = StateTracker::GetInstance()->GetRasterizationState().bits; switch (bpmem.genMode.cullmode) { @@ -1009,7 +1126,7 @@ void Renderer::SetGenerationMode() break; } - m_state_tracker->SetRasterizationState(new_rs_state); + StateTracker::GetInstance()->SetRasterizationState(new_rs_state); } void Renderer::SetDepthMode() @@ -1050,7 +1167,7 @@ void Renderer::SetDepthMode() break; } - m_state_tracker->SetDepthStencilState(new_ds_state); + StateTracker::GetInstance()->SetDepthStencilState(new_ds_state); } void Renderer::SetColorMask() @@ -1066,16 +1183,16 @@ void Renderer::SetColorMask() } BlendState new_blend_state = {}; - new_blend_state.bits = m_state_tracker->GetBlendState().bits; + new_blend_state.bits = StateTracker::GetInstance()->GetBlendState().bits; new_blend_state.write_mask = color_mask; - m_state_tracker->SetBlendState(new_blend_state); + StateTracker::GetInstance()->SetBlendState(new_blend_state); } void Renderer::SetBlendMode(bool force_update) { BlendState new_blend_state = {}; - new_blend_state.bits = m_state_tracker->GetBlendState().bits; + new_blend_state.bits = StateTracker::GetInstance()->GetBlendState().bits; // Fast path for blending disabled if (!bpmem.blendmode.blendenable) @@ -1087,7 +1204,7 @@ void Renderer::SetBlendMode(bool force_update) new_blend_state.alpha_blend_op = VK_BLEND_OP_ADD; new_blend_state.src_alpha_blend = VK_BLEND_FACTOR_ONE; new_blend_state.dst_alpha_blend = VK_BLEND_FACTOR_ZERO; - m_state_tracker->SetBlendState(new_blend_state); + StateTracker::GetInstance()->SetBlendState(new_blend_state); return; } // Fast path for subtract blending @@ -1100,7 +1217,7 @@ void Renderer::SetBlendMode(bool force_update) new_blend_state.alpha_blend_op = VK_BLEND_OP_REVERSE_SUBTRACT; new_blend_state.src_alpha_blend = VK_BLEND_FACTOR_ONE; new_blend_state.dst_alpha_blend = VK_BLEND_FACTOR_ONE; - m_state_tracker->SetBlendState(new_blend_state); + StateTracker::GetInstance()->SetBlendState(new_blend_state); return; } @@ -1195,13 +1312,13 @@ void Renderer::SetBlendMode(bool force_update) new_blend_state.dst_alpha_blend = Util::GetAlphaBlendFactor(new_blend_state.dst_blend); } - m_state_tracker->SetBlendState(new_blend_state); + StateTracker::GetInstance()->SetBlendState(new_blend_state); } void Renderer::SetLogicOpMode() { BlendState new_blend_state = {}; - new_blend_state.bits = m_state_tracker->GetBlendState().bits; + new_blend_state.bits = StateTracker::GetInstance()->GetBlendState().bits; // Does our device support logic ops? bool logic_op_enable = bpmem.blendmode.logicopenable && !bpmem.blendmode.blendenable; @@ -1224,7 +1341,7 @@ void Renderer::SetLogicOpMode() new_blend_state.logic_op = VK_LOGIC_OP_CLEAR; } - m_state_tracker->SetBlendState(new_blend_state); + StateTracker::GetInstance()->SetBlendState(new_blend_state); } else { @@ -1269,7 +1386,7 @@ void Renderer::SetLogicOpMode() new_blend_state.src_alpha_blend = Util::GetAlphaBlendFactor(new_blend_state.src_blend); new_blend_state.dst_alpha_blend = Util::GetAlphaBlendFactor(new_blend_state.dst_blend); - m_state_tracker->SetBlendState(new_blend_state); + StateTracker::GetInstance()->SetBlendState(new_blend_state); } else { @@ -1338,7 +1455,7 @@ void Renderer::SetSamplerState(int stage, int texindex, bool custom_tex) sampler = g_object_cache->GetPointSampler(); } - m_state_tracker->SetSampler(bind_index, sampler); + StateTracker::GetInstance()->SetSampler(bind_index, sampler); m_sampler_states[bind_index].bits = new_state.bits; } @@ -1352,7 +1469,7 @@ void Renderer::ResetSamplerStates() for (size_t i = 0; i < m_sampler_states.size(); i++) { m_sampler_states[i].bits = std::numeric_limits::max(); - m_state_tracker->SetSampler(i, g_object_cache->GetPointSampler()); + StateTracker::GetInstance()->SetSampler(i, g_object_cache->GetPointSampler()); } // Invalidate all sampler objects (some will be unused now). @@ -1375,7 +1492,7 @@ void Renderer::SetScissorRect(const EFBRectangle& rc) {target_rc.left, target_rc.top}, {static_cast(target_rc.GetWidth()), static_cast(target_rc.GetHeight())}}; - m_state_tracker->SetScissor(scissor); + StateTracker::GetInstance()->SetScissor(scissor); } void Renderer::SetViewport() @@ -1420,7 +1537,7 @@ void Renderer::SetViewport() } VkViewport viewport = {x, y, width, height, min_depth, max_depth}; - m_state_tracker->SetViewport(viewport); + StateTracker::GetInstance()->SetViewport(viewport); } void Renderer::ChangeSurface(void* new_surface_handle) diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.h b/Source/Core/VideoBackends/Vulkan/Renderer.h index d3a5d42926..dd2b342ad9 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.h +++ b/Source/Core/VideoBackends/Vulkan/Renderer.h @@ -12,13 +12,14 @@ #include "VideoBackends/Vulkan/Constants.h" #include "VideoCommon/RenderBase.h" +struct XFBSourceBase; + namespace Vulkan { class BoundingBox; class FramebufferManager; class SwapChain; class StagingTexture2D; -class StateTracker; class Texture2D; class RasterFont; @@ -28,10 +29,11 @@ public: Renderer(std::unique_ptr swap_chain); ~Renderer(); + static Renderer* GetInstance(); + SwapChain* GetSwapChain() const { return m_swap_chain.get(); } - StateTracker* GetStateTracker() const { return m_state_tracker.get(); } BoundingBox* GetBoundingBox() const { return m_bounding_box.get(); } - bool Initialize(FramebufferManager* framebuffer_mgr); + bool Initialize(); void RenderText(const std::string& pstr, int left, int top, u32 color) override; u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) override; @@ -88,19 +90,41 @@ 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 DrawFrameDump(const EFBRectangle& rc, u32 xfb_addr, const XFBSourceBase* const* xfb_sources, + u32 xfb_count, u32 fb_width, u32 fb_stride, u32 fb_height); + + // Copies the screenshot readback texture to the frame dumping buffer. + // NOTE: This assumes that DrawScreenshot has been called prior, and the fence associated + // with the command buffer where the readback buffer was populated has been reached. + void DumpFrame(u64 ticks); + + // 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(); - FramebufferManager* m_framebuffer_mgr = nullptr; + + bool ResizeFrameDumpBuffer(u32 new_width, u32 new_height); + void DestroyFrameDumpResources(); VkSemaphore m_image_available_semaphore = VK_NULL_HANDLE; VkSemaphore m_rendering_finished_semaphore = VK_NULL_HANDLE; std::unique_ptr m_swap_chain; - std::unique_ptr m_state_tracker; std::unique_ptr m_bounding_box; std::unique_ptr m_raster_font; @@ -116,8 +140,8 @@ private: VkShaderModule m_blit_fragment_shader = VK_NULL_HANDLE; // Texture used for screenshot/frame dumping - std::unique_ptr m_screenshot_render_texture; - std::unique_ptr m_screenshot_readback_texture; - VkFramebuffer m_screenshot_framebuffer = VK_NULL_HANDLE; + std::unique_ptr m_frame_dump_render_texture; + std::unique_ptr m_frame_dump_readback_texture; + VkFramebuffer m_frame_dump_framebuffer = VK_NULL_HANDLE; }; } diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp index 84b1099862..bfd198886b 100644 --- a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp +++ b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp @@ -24,10 +24,33 @@ namespace Vulkan { -StateTracker::StateTracker() +static std::unique_ptr s_state_tracker; + +StateTracker* StateTracker::GetInstance() +{ + return s_state_tracker.get(); +} + +bool StateTracker::CreateInstance() +{ + _assert_(!s_state_tracker); + s_state_tracker = std::make_unique(); + if (!s_state_tracker->Initialize()) + { + s_state_tracker.reset(); + return false; + } + return true; +} + +void StateTracker::DestroyInstance() +{ + s_state_tracker.reset(); +} + +bool StateTracker::Initialize() { // Set some sensible defaults - m_pipeline_state.pipeline_layout = g_object_cache->GetStandardPipelineLayout(); m_pipeline_state.rasterization_state.cull_mode = VK_CULL_MODE_NONE; m_pipeline_state.rasterization_state.per_sample_shading = VK_FALSE; m_pipeline_state.rasterization_state.depth_clamp = VK_FALSE; @@ -68,7 +91,10 @@ StateTracker::StateTracker() StreamBuffer::Create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, INITIAL_UNIFORM_STREAM_BUFFER_SIZE, MAXIMUM_UNIFORM_STREAM_BUFFER_SIZE); if (!m_uniform_stream_buffer) + { PanicAlert("Failed to create uniform stream buffer"); + return false; + } // The validation layer complains if max(offsets) + max(ubo_ranges) >= ubo_size. // To work around this we reserve the maximum buffer size at all times, but only commit @@ -87,10 +113,7 @@ StateTracker::StateTracker() // Set default constants UploadAllConstants(); -} - -StateTracker::~StateTracker() -{ + return true; } void StateTracker::SetVertexBuffer(VkBuffer buffer, VkDeviceSize offset) @@ -372,7 +395,7 @@ void StateTracker::UploadAllConstants() // If this fails, wait until the GPU has caught up. // The only places that call constant updates are safe to have state restored. WARN_LOG(VIDEO, "Executing command list while waiting for space in uniform buffer"); - Util::ExecuteCurrentCommandsAndRestoreState(this, false); + Util::ExecuteCurrentCommandsAndRestoreState(false); if (!m_uniform_stream_buffer->ReserveMemory(total_allocation_size, g_vulkan_context->GetUniformBufferAlignment(), true, true, false)) diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.h b/Source/Core/VideoBackends/Vulkan/StateTracker.h index a763c72b7f..b31adec6b5 100644 --- a/Source/Core/VideoBackends/Vulkan/StateTracker.h +++ b/Source/Core/VideoBackends/Vulkan/StateTracker.h @@ -24,8 +24,12 @@ class VertexFormat; class StateTracker { public: - StateTracker(); - ~StateTracker(); + StateTracker() = default; + ~StateTracker() = default; + + static StateTracker* GetInstance(); + static bool CreateInstance(); + static void DestroyInstance(); const RasterizationState& GetRasterizationState() const { @@ -108,6 +112,8 @@ public: bool IsWithinRenderArea(s32 x, s32 y, u32 width, u32 height) const; private: + bool Initialize(); + // Check that the specified viewport is within the render area. // If not, ends the render pass if it is a clear render pass. bool IsViewportWithinRenderArea() const; 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(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(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(scaled_src_rect.GetWidth()), static_cast(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& dst_rect, + Texture2D* src_texture, + const MathUtil::Rectangle& 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& dst_rect, + Texture2D* src_texture, + const MathUtil::Rectangle& src_rect) +{ + _assert_msg_(VIDEO, static_cast(src_rect.GetWidth()) <= src_texture->GetWidth() && + static_cast(src_rect.GetHeight()) <= src_texture->GetHeight(), + "Source rect is too large for CopyRectangleFromTexture"); + + _assert_msg_(VIDEO, static_cast(dst_rect.GetWidth()) <= dst_texture->config.width && + static_cast(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(src_rect.GetWidth()), static_cast(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& dst_rect, + Texture2D* src_texture, + const MathUtil::Rectangle& 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(dst_rect.GetWidth()), static_cast(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(src_texture->GetWidth()), + static_cast(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 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(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(scaled_src_rect.GetWidth()), static_cast(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& dst_rect) { const TCacheEntry* source_vk = static_cast(source); - VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); + TextureCache::GetInstance()->CopyRectangleFromTexture(this, dst_rect, source_vk->GetTexture(), + src_rect); - // 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(src_rect.GetWidth()) <= source->config.width && - static_cast(src_rect.GetHeight()) <= source->config.height, - "Source rect is too large for CopyRectangleFromTexture"); - - _assert_msg_(VIDEO, static_cast(dst_rect.GetWidth()) <= config.width && - static_cast(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(src_rect.GetWidth()), static_cast(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(); - - // 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(dst_rect.GetWidth()), static_cast(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& src_rect) +{ + StateTracker::GetInstance()->EndRenderPass(); + + VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); + src_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + // Borrow framebuffer from EFB2RAM encoder. + Texture2D* encoding_texture = m_texture_encoder->GetEncodingTexture(); + StagingTexture2D* download_texture = m_texture_encoder->GetDownloadTexture(); + encoding_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + // Use fragment shader to convert RGBA to YUYV. + // Use linear sampler for downscaling. This texture is in BGRA order, so the data is already in + // the order the guest is expecting and we don't have to swap it at readback time. The width + // is halved because we're using an RGBA8 texture, but the YUYV data is two bytes per pixel. + u32 output_width = dst_width / 2; + UtilityShaderDraw draw(command_buffer, g_object_cache->GetStandardPipelineLayout(), + m_texture_encoder->GetEncodingRenderPass(), + g_object_cache->GetPassthroughVertexShader(), VK_NULL_HANDLE, + m_rgb_to_yuyv_shader); + VkRect2D region = {{0, 0}, {output_width, dst_height}}; + draw.BeginRenderPass(m_texture_encoder->GetEncodingTextureFramebuffer(), region); + draw.SetPSSampler(0, src_texture->GetView(), g_object_cache->GetPointSampler()); + draw.DrawQuad(0, 0, static_cast(output_width), static_cast(dst_height), src_rect.left, + src_rect.top, 0, src_rect.GetWidth(), src_rect.GetHeight(), + static_cast(src_texture->GetWidth()), + static_cast(src_texture->GetHeight())); + draw.EndRenderPass(); + + // Render pass transitions to TRANSFER_SRC. + encoding_texture->OverrideImageLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + + // Copy from encoding texture to download buffer. + download_texture->CopyFromImage(command_buffer, encoding_texture->GetImage(), + VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, output_width, dst_height, 0, 0); + Util::ExecuteCurrentCommandsAndRestoreState(false, true); + + // Finally, copy to guest memory. This may have a different stride. + download_texture->ReadTexels(0, 0, output_width, dst_height, dst_ptr, dst_stride); +} + +void TextureCache::DecodeYUYVTextureFromMemory(TCacheEntry* dst_texture, const void* src_ptr, + u32 src_width, u32 src_stride, u32 src_height) +{ + // Copies (and our decoding step) cannot be done inside a render pass. + StateTracker::GetInstance()->EndRenderPass(); + + // We share the upload buffer with normal textures here, since the XFB buffers aren't very large. + u32 upload_size = src_stride * src_height; + if (!m_texture_upload_buffer->ReserveMemory(upload_size, + g_vulkan_context->GetBufferImageGranularity())) + { + // Execute the command buffer first. + WARN_LOG(VIDEO, "Executing command list while waiting for space in texture upload buffer"); + Util::ExecuteCurrentCommandsAndRestoreState(false); + if (!m_texture_upload_buffer->ReserveMemory(upload_size, + g_vulkan_context->GetBufferImageGranularity())) + PanicAlert("Failed to allocate space in texture upload buffer"); + } + + // Assume that each source row is not padded. + _assert_(src_stride == (src_width * sizeof(u16))); + VkDeviceSize image_upload_buffer_offset = m_texture_upload_buffer->GetCurrentOffset(); + std::memcpy(m_texture_upload_buffer->GetCurrentHostPointer(), src_ptr, upload_size); + m_texture_upload_buffer->CommitMemory(upload_size); + + // Copy from the upload buffer to the intermediate texture. We borrow this from the encoder. + // The width is specified as half here because we have two pixels packed in each RGBA texel. + // In the future this could be skipped by reading the upload buffer as a uniform texel buffer. + VkBufferImageCopy image_copy = { + image_upload_buffer_offset, // VkDeviceSize bufferOffset + 0, // uint32_t bufferRowLength + 0, // uint32_t bufferImageHeight + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, // VkImageSubresourceLayers imageSubresource + {0, 0, 0}, // VkOffset3D imageOffset + {src_width / 2, src_height, 1} // VkExtent3D imageExtent + }; + VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer(); + Texture2D* intermediate_texture = m_texture_encoder->GetEncodingTexture(); + intermediate_texture->TransitionToLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + vkCmdCopyBufferToImage(command_buffer, m_texture_upload_buffer->GetBuffer(), + intermediate_texture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, + &image_copy); + intermediate_texture->TransitionToLayout(command_buffer, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + dst_texture->GetTexture()->TransitionToLayout(command_buffer, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + // Convert from the YUYV data now in the intermediate texture to RGBA in the destination. + UtilityShaderDraw draw(command_buffer, g_object_cache->GetStandardPipelineLayout(), + m_texture_encoder->GetEncodingRenderPass(), + g_object_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE, + m_yuyv_to_rgb_shader); + VkRect2D region = {{0, 0}, {src_width, src_height}}; + draw.BeginRenderPass(dst_texture->GetFramebuffer(), region); + draw.SetViewportAndScissor(0, 0, static_cast(src_width), static_cast(src_height)); + draw.SetPSSampler(0, intermediate_texture->GetView(), g_object_cache->GetPointSampler()); + draw.DrawWithoutVertexBuffer(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 4); + draw.EndRenderPass(); } } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/TextureCache.h b/Source/Core/VideoBackends/Vulkan/TextureCache.h index 18c8bd6020..5da9515991 100644 --- a/Source/Core/VideoBackends/Vulkan/TextureCache.h +++ b/Source/Core/VideoBackends/Vulkan/TextureCache.h @@ -20,25 +20,10 @@ class TextureEncoder; class TextureCache : public TextureCacheBase { public: - TextureCache(); - ~TextureCache(); - - bool Initialize(StateTracker* state_tracker); - - bool CompileShaders() override; - void DeleteShaders() override; - void ConvertTexture(TCacheEntryBase* base_entry, TCacheEntryBase* base_unconverted, void* palette, - TlutFormat format) override; - - void 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) override; - -private: struct TCacheEntry : TCacheEntryBase { - TCacheEntry(const TCacheEntryConfig& config_, TextureCache* parent, - std::unique_ptr texture, VkFramebuffer framebuffer); + TCacheEntry(const TCacheEntryConfig& config_, std::unique_ptr texture, + VkFramebuffer framebuffer); ~TCacheEntry(); Texture2D* GetTexture() const { return m_texture.get(); } @@ -55,20 +40,51 @@ private: bool Save(const std::string& filename, unsigned int level) override; private: - TextureCache* m_parent; std::unique_ptr m_texture; - - // If we're an EFB copy, framebuffer for drawing into. VkFramebuffer m_framebuffer; }; + TextureCache(); + ~TextureCache(); + + static TextureCache* GetInstance(); + + bool Initialize(); + + bool CompileShaders() override; + void DeleteShaders() override; + TCacheEntryBase* CreateTexture(const TCacheEntryConfig& config) override; - bool CreateRenderPasses(); + void ConvertTexture(TCacheEntryBase* base_entry, TCacheEntryBase* base_unconverted, void* palette, + TlutFormat format) override; + void 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) override; + + void CopyRectangleFromTexture(TCacheEntry* dst_texture, const MathUtil::Rectangle& dst_rect, + Texture2D* src_texture, const MathUtil::Rectangle& src_rect); + + // Encodes texture to guest memory in XFB (YUYV) format. + void EncodeYUYVTextureToMemory(void* dst_ptr, u32 dst_width, u32 dst_stride, u32 dst_height, + Texture2D* src_texture, const MathUtil::Rectangle& src_rect); + + // Decodes data from guest memory in XFB (YUYV) format to a RGBA format texture on the GPU. + void DecodeYUYVTextureFromMemory(TCacheEntry* dst_texture, const void* src_ptr, u32 src_width, + u32 src_stride, u32 src_height); + +private: + bool CreateRenderPasses(); VkRenderPass GetRenderPassForTextureUpdate(const Texture2D* texture) const; - StateTracker* m_state_tracker = nullptr; + // Copies the contents of a texture using vkCmdCopyImage + void CopyTextureRectangle(TCacheEntry* dst_texture, const MathUtil::Rectangle& dst_rect, + Texture2D* src_texture, const MathUtil::Rectangle& src_rect); + + // Copies (and optionally scales) the contents of a texture using a framgent shader. + void ScaleTextureRectangle(TCacheEntry* dst_texture, const MathUtil::Rectangle& dst_rect, + Texture2D* src_texture, const MathUtil::Rectangle& src_rect); VkRenderPass m_initialize_render_pass = VK_NULL_HANDLE; VkRenderPass m_update_render_pass = VK_NULL_HANDLE; @@ -82,6 +98,8 @@ private: VkShaderModule m_copy_shader = VK_NULL_HANDLE; VkShaderModule m_efb_color_to_tex_shader = VK_NULL_HANDLE; VkShaderModule m_efb_depth_to_tex_shader = VK_NULL_HANDLE; + VkShaderModule m_rgb_to_yuyv_shader = VK_NULL_HANDLE; + VkShaderModule m_yuyv_to_rgb_shader = VK_NULL_HANDLE; }; } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/TextureEncoder.cpp b/Source/Core/VideoBackends/Vulkan/TextureEncoder.cpp index 0771d37193..404b30b09c 100644 --- a/Source/Core/VideoBackends/Vulkan/TextureEncoder.cpp +++ b/Source/Core/VideoBackends/Vulkan/TextureEncoder.cpp @@ -73,11 +73,11 @@ bool TextureEncoder::Initialize() return true; } -void TextureEncoder::EncodeTextureToRam(StateTracker* state_tracker, VkImageView src_texture, - u8* dest_ptr, u32 format, u32 native_width, - u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride, - PEControl::PixelFormat src_format, bool is_intensity, - int scale_by_half, const EFBRectangle& src_rect) +void TextureEncoder::EncodeTextureToRam(VkImageView src_texture, u8* dest_ptr, u32 format, + u32 native_width, u32 bytes_per_row, u32 num_blocks_y, + u32 memory_stride, PEControl::PixelFormat src_format, + bool is_intensity, int scale_by_half, + const EFBRectangle& src_rect) { if (m_texture_encoding_shaders[format] == VK_NULL_HANDLE) { @@ -86,7 +86,7 @@ void TextureEncoder::EncodeTextureToRam(StateTracker* state_tracker, VkImageView } // Can't do our own draw within a render pass. - state_tracker->EndRenderPass(); + StateTracker::GetInstance()->EndRenderPass(); UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), g_object_cache->GetPushConstantPipelineLayout(), m_encoding_render_pass, @@ -122,8 +122,8 @@ void TextureEncoder::EncodeTextureToRam(StateTracker* state_tracker, VkImageView // Block until the GPU has finished copying to the staging texture. g_command_buffer_mgr->ExecuteCommandBuffer(false, true); - state_tracker->InvalidateDescriptorSets(); - state_tracker->SetPendingRebind(); + StateTracker::GetInstance()->InvalidateDescriptorSets(); + StateTracker::GetInstance()->SetPendingRebind(); // Copy from staging texture to the final destination, adjusting pitch if necessary. m_download_texture->ReadTexels(0, 0, render_width, render_height, dest_ptr, memory_stride); @@ -197,7 +197,8 @@ bool TextureEncoder::CreateEncodingTexture() m_encoding_texture = Texture2D::Create( ENCODING_TEXTURE_WIDTH, ENCODING_TEXTURE_HEIGHT, 1, 1, ENCODING_TEXTURE_FORMAT, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT); + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); if (!m_encoding_texture) return false; diff --git a/Source/Core/VideoBackends/Vulkan/TextureEncoder.h b/Source/Core/VideoBackends/Vulkan/TextureEncoder.h index daea8b59ff..c3fa68c752 100644 --- a/Source/Core/VideoBackends/Vulkan/TextureEncoder.h +++ b/Source/Core/VideoBackends/Vulkan/TextureEncoder.h @@ -15,7 +15,6 @@ namespace Vulkan { class StagingTexture2D; -class StateTracker; class Texture2D; class TextureEncoder @@ -24,15 +23,19 @@ 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. // Assumes that no render pass is currently in progress. // WARNING: Executes the current command buffer. - void EncodeTextureToRam(StateTracker* state_tracker, VkImageView src_texture, u8* dest_ptr, - u32 format, u32 native_width, u32 bytes_per_row, u32 num_blocks_y, - u32 memory_stride, PEControl::PixelFormat src_format, bool is_intensity, - int scale_by_half, const EFBRectangle& source); + void EncodeTextureToRam(VkImageView src_texture, u8* dest_ptr, u32 format, u32 native_width, + u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride, + PEControl::PixelFormat src_format, bool is_intensity, int scale_by_half, + const EFBRectangle& source); private: // From OGL. diff --git a/Source/Core/VideoBackends/Vulkan/Util.cpp b/Source/Core/VideoBackends/Vulkan/Util.cpp index 9ef432d779..2e8025dbc1 100644 --- a/Source/Core/VideoBackends/Vulkan/Util.cpp +++ b/Source/Core/VideoBackends/Vulkan/Util.cpp @@ -195,13 +195,12 @@ void BufferMemoryBarrier(VkCommandBuffer command_buffer, VkBuffer buffer, &buffer_info, 0, nullptr); } -void ExecuteCurrentCommandsAndRestoreState(StateTracker* state_tracker, bool execute_off_thread, - bool wait_for_completion) +void ExecuteCurrentCommandsAndRestoreState(bool execute_off_thread, bool wait_for_completion) { - state_tracker->EndRenderPass(); + StateTracker::GetInstance()->EndRenderPass(); g_command_buffer_mgr->ExecuteCommandBuffer(execute_off_thread, wait_for_completion); - state_tracker->InvalidateDescriptorSets(); - state_tracker->SetPendingRebind(); + StateTracker::GetInstance()->InvalidateDescriptorSets(); + StateTracker::GetInstance()->SetPendingRebind(); } VkShaderModule CreateShaderModule(const u32* spv, size_t spv_word_count) diff --git a/Source/Core/VideoBackends/Vulkan/Util.h b/Source/Core/VideoBackends/Vulkan/Util.h index e9dec51700..6900cce08b 100644 --- a/Source/Core/VideoBackends/Vulkan/Util.h +++ b/Source/Core/VideoBackends/Vulkan/Util.h @@ -47,7 +47,7 @@ void BufferMemoryBarrier(VkCommandBuffer command_buffer, VkBuffer buffer, // Completes the current render pass, executes the command buffer, and restores state ready for next // render. Use when you want to kick the current buffer to make room for new data. -void ExecuteCurrentCommandsAndRestoreState(StateTracker* state_tracker, bool execute_off_thread, +void ExecuteCurrentCommandsAndRestoreState(bool execute_off_thread, bool wait_for_completion = false); // Create a shader module from the specified SPIR-V. diff --git a/Source/Core/VideoBackends/Vulkan/VertexManager.cpp b/Source/Core/VideoBackends/Vulkan/VertexManager.cpp index 02be335aea..8300fb5fdd 100644 --- a/Source/Core/VideoBackends/Vulkan/VertexManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/VertexManager.cpp @@ -36,10 +36,13 @@ VertexManager::~VertexManager() { } -bool VertexManager::Initialize(StateTracker* state_tracker) +VertexManager* VertexManager::GetInstance() { - m_state_tracker = state_tracker; + return static_cast(g_vertex_manager.get()); +} +bool VertexManager::Initialize() +{ m_vertex_stream_buffer = StreamBuffer::Create(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, INITIAL_VERTEX_BUFFER_SIZE, MAX_VERTEX_BUFFER_SIZE); @@ -72,8 +75,9 @@ void VertexManager::PrepareDrawBuffers(u32 stride) ADDSTAT(stats.thisFrame.bytesVertexStreamed, static_cast(vertex_data_size)); ADDSTAT(stats.thisFrame.bytesIndexStreamed, static_cast(index_data_size)); - m_state_tracker->SetVertexBuffer(m_vertex_stream_buffer->GetBuffer(), 0); - m_state_tracker->SetIndexBuffer(m_index_stream_buffer->GetBuffer(), 0, VK_INDEX_TYPE_UINT16); + StateTracker::GetInstance()->SetVertexBuffer(m_vertex_stream_buffer->GetBuffer(), 0); + StateTracker::GetInstance()->SetIndexBuffer(m_index_stream_buffer->GetBuffer(), 0, + VK_INDEX_TYPE_UINT16); } void VertexManager::ResetBuffer(u32 stride) @@ -94,7 +98,7 @@ void VertexManager::ResetBuffer(u32 stride) { // Flush any pending commands first, so that we can wait on the fences WARN_LOG(VIDEO, "Executing command list while waiting for space in vertex/index buffer"); - Util::ExecuteCurrentCommandsAndRestoreState(m_state_tracker, false); + Util::ExecuteCurrentCommandsAndRestoreState(false); // Attempt to allocate again, this may cause a fence wait if (!has_vbuffer_allocation) @@ -133,21 +137,21 @@ void VertexManager::vFlush(bool use_dst_alpha) u32 index_count = IndexGenerator::GetIndexLen(); // Update assembly state - m_state_tracker->SetVertexFormat(vertex_format); + StateTracker::GetInstance()->SetVertexFormat(vertex_format); switch (m_current_primitive_type) { case PRIMITIVE_POINTS: - m_state_tracker->SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST); - m_state_tracker->DisableBackFaceCulling(); + StateTracker::GetInstance()->SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST); + StateTracker::GetInstance()->DisableBackFaceCulling(); break; case PRIMITIVE_LINES: - m_state_tracker->SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_LINE_LIST); - m_state_tracker->DisableBackFaceCulling(); + StateTracker::GetInstance()->SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_LINE_LIST); + StateTracker::GetInstance()->DisableBackFaceCulling(); break; case PRIMITIVE_TRIANGLES: - m_state_tracker->SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP); + StateTracker::GetInstance()->SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP); g_renderer->SetGenerationMode(); break; } @@ -158,37 +162,34 @@ void VertexManager::vFlush(bool use_dst_alpha) dstalpha_mode = DSTALPHA_DUAL_SOURCE_BLEND; // Check for any shader stage changes - m_state_tracker->CheckForShaderChanges(m_current_primitive_type, dstalpha_mode); + StateTracker::GetInstance()->CheckForShaderChanges(m_current_primitive_type, dstalpha_mode); // Update any changed constants - m_state_tracker->UpdateVertexShaderConstants(); - m_state_tracker->UpdateGeometryShaderConstants(); - m_state_tracker->UpdatePixelShaderConstants(); + StateTracker::GetInstance()->UpdateVertexShaderConstants(); + StateTracker::GetInstance()->UpdateGeometryShaderConstants(); + StateTracker::GetInstance()->UpdatePixelShaderConstants(); // Flush all EFB pokes and invalidate the peek cache. - // TODO: Cleaner way without the cast. - FramebufferManager* framebuffer_mgr = - static_cast(g_framebuffer_manager.get()); - framebuffer_mgr->InvalidatePeekCache(); - framebuffer_mgr->FlushEFBPokes(m_state_tracker); + FramebufferManager::GetInstance()->InvalidatePeekCache(); + FramebufferManager::GetInstance()->FlushEFBPokes(); // If bounding box is enabled, we need to flush any changes first, then invalidate what we have. if (g_vulkan_context->SupportsBoundingBox()) { - BoundingBox* bounding_box = static_cast(g_renderer.get())->GetBoundingBox(); + BoundingBox* bounding_box = Renderer::GetInstance()->GetBoundingBox(); bool bounding_box_enabled = (::BoundingBox::active && g_ActiveConfig.bBBoxEnable); if (bounding_box_enabled) { - bounding_box->Flush(m_state_tracker); - bounding_box->Invalidate(m_state_tracker); + bounding_box->Flush(); + bounding_box->Invalidate(); } // Update which descriptor set/pipeline layout to use. - m_state_tracker->SetBBoxEnable(bounding_box_enabled); + StateTracker::GetInstance()->SetBBoxEnable(bounding_box_enabled); } // Bind all pending state to the command buffer - if (!m_state_tracker->Bind()) + if (!StateTracker::GetInstance()->Bind()) { WARN_LOG(VIDEO, "Skipped draw of %u indices", index_count); return; @@ -207,8 +208,9 @@ void VertexManager::vFlush(bool use_dst_alpha) bool logic_op_enabled = bpmem.blendmode.logicopenable && !bpmem.blendmode.blendenable; if (use_dst_alpha && (!g_vulkan_context->SupportsDualSourceBlend() || logic_op_enabled)) { - m_state_tracker->CheckForShaderChanges(m_current_primitive_type, DSTALPHA_ALPHA_PASS); - if (!m_state_tracker->Bind()) + StateTracker::GetInstance()->CheckForShaderChanges(m_current_primitive_type, + DSTALPHA_ALPHA_PASS); + if (!StateTracker::GetInstance()->Bind()) { WARN_LOG(VIDEO, "Skipped draw of %u indices (alpha pass)", index_count); return; @@ -218,7 +220,7 @@ void VertexManager::vFlush(bool use_dst_alpha) m_current_draw_base_index, m_current_draw_base_vertex, 0); } - m_state_tracker->OnDraw(); + StateTracker::GetInstance()->OnDraw(); } } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VertexManager.h b/Source/Core/VideoBackends/Vulkan/VertexManager.h index 0de4faae85..b79e671c6c 100644 --- a/Source/Core/VideoBackends/Vulkan/VertexManager.h +++ b/Source/Core/VideoBackends/Vulkan/VertexManager.h @@ -12,7 +12,6 @@ namespace Vulkan { -class StateTracker; class StreamBuffer; class VertexManager : public VertexManagerBase @@ -21,7 +20,9 @@ public: VertexManager(); ~VertexManager(); - bool Initialize(StateTracker* state_tracker); + static VertexManager* GetInstance(); + + bool Initialize(); NativeVertexFormat* CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override; @@ -32,8 +33,6 @@ protected: private: void vFlush(bool use_dst_alpha) override; - StateTracker* m_state_tracker = nullptr; - std::vector m_cpu_vertex_buffer; std::vector m_cpu_index_buffer; diff --git a/Source/Core/VideoBackends/Vulkan/main.cpp b/Source/Core/VideoBackends/Vulkan/main.cpp index 1fb8f6c6ab..4a70a6e509 100644 --- a/Source/Core/VideoBackends/Vulkan/main.cpp +++ b/Source/Core/VideoBackends/Vulkan/main.cpp @@ -194,19 +194,16 @@ bool VideoBackend::Initialize(void* window_handle) g_framebuffer_manager = std::make_unique(); g_renderer = std::make_unique(std::move(swap_chain)); - // Cast to our wrapper classes, so we can call the init methods. - Renderer* renderer = static_cast(g_renderer.get()); - FramebufferManager* framebuffer_mgr = - static_cast(g_framebuffer_manager.get()); - // Invoke init methods on main wrapper classes. // These have to be done before the others because the destructors // for the remaining classes may call methods on these. - if (!g_object_cache->Initialize() || !framebuffer_mgr->Initialize() || - !renderer->Initialize(framebuffer_mgr)) + if (!g_object_cache->Initialize() || !FramebufferManager::GetInstance()->Initialize() || + !StateTracker::CreateInstance() || !Renderer::GetInstance()->Initialize()) { PanicAlert("Failed to initialize Vulkan classes."); g_renderer.reset(); + StateTracker::DestroyInstance(); + g_framebuffer_manager.reset(); g_object_cache.reset(); g_command_buffer_mgr.reset(); g_vulkan_context.reset(); @@ -219,18 +216,16 @@ bool VideoBackend::Initialize(void* window_handle) g_vertex_manager = std::make_unique(); g_texture_cache = std::make_unique(); g_perf_query = std::make_unique(); - VertexManager* vertex_manager = static_cast(g_vertex_manager.get()); - TextureCache* texture_cache = static_cast(g_texture_cache.get()); - PerfQuery* perf_query = static_cast(g_perf_query.get()); - if (!vertex_manager->Initialize(renderer->GetStateTracker()) || - !texture_cache->Initialize(renderer->GetStateTracker()) || - !perf_query->Initialize(renderer->GetStateTracker())) + if (!VertexManager::GetInstance()->Initialize() || !TextureCache::GetInstance()->Initialize() || + !PerfQuery::GetInstance()->Initialize()) { PanicAlert("Failed to initialize Vulkan classes."); g_perf_query.reset(); g_texture_cache.reset(); g_vertex_manager.reset(); g_renderer.reset(); + StateTracker::DestroyInstance(); + g_framebuffer_manager.reset(); g_object_cache.reset(); g_command_buffer_mgr.reset(); g_vulkan_context.reset(); @@ -273,11 +268,12 @@ void VideoBackend::Video_Cleanup() // Save all cached pipelines out to disk for next time. g_object_cache->SavePipelineCache(); - g_texture_cache.reset(); g_perf_query.reset(); + g_texture_cache.reset(); g_vertex_manager.reset(); - g_renderer.reset(); g_framebuffer_manager.reset(); + StateTracker::DestroyInstance(); + g_renderer.reset(); CleanupShared(); }