diff --git a/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp b/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp index 489443f9b7..97de14849b 100644 --- a/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp @@ -137,9 +137,19 @@ bool FramebufferManager::CreateEFBRenderPass() 0, nullptr}; - VkResult res = - vkCreateRenderPass(g_vulkan_context->GetDevice(), &pass_info, nullptr, &m_efb_render_pass); + VkResult res = vkCreateRenderPass(g_vulkan_context->GetDevice(), &pass_info, nullptr, + &m_efb_load_render_pass); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateRenderPass (EFB) failed: "); + return false; + } + // render pass for clearing color/depth on load, as opposed to loading it + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + res = vkCreateRenderPass(g_vulkan_context->GetDevice(), &pass_info, nullptr, + &m_efb_clear_render_pass); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateRenderPass (EFB) failed: "); @@ -189,10 +199,10 @@ bool FramebufferManager::CreateEFBRenderPass() void FramebufferManager::DestroyEFBRenderPass() { - if (m_efb_render_pass != VK_NULL_HANDLE) + if (m_efb_load_render_pass != VK_NULL_HANDLE) { - vkDestroyRenderPass(g_vulkan_context->GetDevice(), m_efb_render_pass, nullptr); - m_efb_render_pass = VK_NULL_HANDLE; + vkDestroyRenderPass(g_vulkan_context->GetDevice(), m_efb_load_render_pass, nullptr); + m_efb_load_render_pass = VK_NULL_HANDLE; } if (m_depth_resolve_render_pass != VK_NULL_HANDLE) @@ -280,7 +290,7 @@ bool FramebufferManager::CreateEFBFramebuffer() VkFramebufferCreateInfo framebuffer_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, nullptr, 0, - m_efb_render_pass, + m_efb_load_render_pass, static_cast(ArraySize(framebuffer_attachments)), framebuffer_attachments, m_efb_width, @@ -398,7 +408,7 @@ void FramebufferManager::ReinterpretPixelData(int convtype) VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(), - g_object_cache->GetStandardPipelineLayout(), m_efb_render_pass, + g_object_cache->GetStandardPipelineLayout(), m_efb_load_render_pass, g_object_cache->GetScreenQuadVertexShader(), g_object_cache->GetScreenQuadGeometryShader(), pixel_shader); @@ -1136,7 +1146,7 @@ void FramebufferManager::DrawPokeVertices(StateTracker* state_tracker, pipeline_info.vs = m_poke_vertex_shader; pipeline_info.gs = (m_efb_layers > 1) ? m_poke_geometry_shader : VK_NULL_HANDLE; pipeline_info.ps = m_poke_fragment_shader; - pipeline_info.render_pass = m_efb_render_pass; + pipeline_info.render_pass = m_efb_load_render_pass; pipeline_info.rasterization_state.bits = Util::GetNoCullRasterizationState().bits; pipeline_info.depth_stencil_state.bits = Util::GetNoDepthTestingDepthStencilState().bits; pipeline_info.blend_state.bits = Util::GetNoBlendingBlendState().bits; @@ -1184,6 +1194,7 @@ 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(); Util::SetViewportAndScissor(command_buffer, 0, 0, m_efb_width, m_efb_height); diff --git a/Source/Core/VideoBackends/Vulkan/FramebufferManager.h b/Source/Core/VideoBackends/Vulkan/FramebufferManager.h index e43fc0ce32..3ae8b90f05 100644 --- a/Source/Core/VideoBackends/Vulkan/FramebufferManager.h +++ b/Source/Core/VideoBackends/Vulkan/FramebufferManager.h @@ -30,7 +30,8 @@ public: bool Initialize(); - VkRenderPass GetEFBRenderPass() const { return m_efb_render_pass; } + VkRenderPass GetEFBLoadRenderPass() const { return m_efb_load_render_pass; } + VkRenderPass GetEFBClearRenderPass() const { return m_efb_clear_render_pass; } u32 GetEFBWidth() const { return m_efb_width; } u32 GetEFBHeight() const { return m_efb_height; } u32 GetEFBLayers() const { return m_efb_layers; } @@ -118,7 +119,8 @@ private: void DrawPokeVertices(StateTracker* state_tracker, const EFBPokeVertex* vertices, size_t vertex_count, bool write_color, bool write_depth); - VkRenderPass m_efb_render_pass = VK_NULL_HANDLE; + VkRenderPass m_efb_load_render_pass = VK_NULL_HANDLE; + VkRenderPass m_efb_clear_render_pass = VK_NULL_HANDLE; VkRenderPass m_depth_resolve_render_pass = VK_NULL_HANDLE; u32 m_efb_width = 0; diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp index 38850322b6..a8578d0b27 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp @@ -336,6 +336,18 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha { // Native -> EFB coordinates TargetRectangle target_rc = Renderer::ConvertEFBRectangle(rc); + VkRect2D target_vk_rc = { + {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. @@ -348,55 +360,71 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha color |= 0xFF000000; } - // Fast path: when both color and alpha are enabled, we can blow away the entire buffer - VkClearAttachment clear_attachments[2]; - uint32_t num_clear_attachments = 0; - if (color_enable && alpha_enable) + // 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()) { - clear_attachments[num_clear_attachments].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - clear_attachments[num_clear_attachments].colorAttachment = 0; - clear_attachments[num_clear_attachments].clearValue.color.float32[0] = - float((color >> 16) & 0xFF) / 255.0f; - clear_attachments[num_clear_attachments].clearValue.color.float32[1] = - float((color >> 8) & 0xFF) / 255.0f; - clear_attachments[num_clear_attachments].clearValue.color.float32[2] = - float((color >> 0) & 0xFF) / 255.0f; - clear_attachments[num_clear_attachments].clearValue.color.float32[3] = - float((color >> 24) & 0xFF) / 255.0f; - num_clear_attachments++; - color_enable = false; - alpha_enable = false; - } - if (z_enable) - { - clear_attachments[num_clear_attachments].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - clear_attachments[num_clear_attachments].colorAttachment = 0; - clear_attachments[num_clear_attachments].clearValue.depthStencil.depth = - 1.0f - (float(z & 0xFFFFFF) / 16777216.0f); - clear_attachments[num_clear_attachments].clearValue.depthStencil.stencil = 0; - num_clear_attachments++; - z_enable = false; - } - if (num_clear_attachments > 0) - { - VkClearRect rect = {}; - rect.rect.offset = {target_rc.left, target_rc.top}; - rect.rect.extent = {static_cast(target_rc.GetWidth()), - static_cast(target_rc.GetHeight())}; - - rect.baseArrayLayer = 0; - rect.layerCount = m_framebuffer_mgr->GetEFBLayers(); - - m_state_tracker->BeginRenderPass(); - - vkCmdClearAttachments(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_clear_attachments, - clear_attachments, 1, &rect); + // Prefer not to end a render pass just to do a clear. + use_clear_render_pass = false; } + // Fastest path: Use a render pass to clear the buffers. + if (use_clear_render_pass) + { + VkClearValue clear_values[2] = {clear_color_value, clear_depth_value}; + m_state_tracker->BeginClearRenderPass(target_vk_rc, clear_values); + return; + } + + // Fast path: Use vkCmdClearAttachments to clear the buffers within a render path + // We can't use this when preserving alpha but clearing color. + { + VkClearAttachment clear_attachments[2]; + uint32_t num_clear_attachments = 0; + if (color_enable && alpha_enable) + { + clear_attachments[num_clear_attachments].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + clear_attachments[num_clear_attachments].colorAttachment = 0; + clear_attachments[num_clear_attachments].clearValue = clear_color_value; + num_clear_attachments++; + color_enable = false; + alpha_enable = false; + } + if (z_enable) + { + clear_attachments[num_clear_attachments].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + clear_attachments[num_clear_attachments].colorAttachment = 0; + clear_attachments[num_clear_attachments].clearValue = clear_depth_value; + num_clear_attachments++; + z_enable = false; + } + 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)) + { + m_state_tracker->EndClearRenderPass(); + } + m_state_tracker->BeginRenderPass(); + + vkCmdClearAttachments(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_clear_attachments, + clear_attachments, 1, &clear_rect); + } + } + + // Anything left over for the slow path? if (!color_enable && !alpha_enable && !z_enable) 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)) + { + m_state_tracker->EndClearRenderPass(); + } m_state_tracker->BeginRenderPass(); m_state_tracker->SetPendingRebind(); @@ -421,21 +449,17 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha // 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->GetEFBRenderPass(), g_object_cache->GetPassthroughVertexShader(), + m_framebuffer_mgr->GetEFBLoadRenderPass(), g_object_cache->GetPassthroughVertexShader(), g_object_cache->GetPassthroughGeometryShader(), m_clear_fragment_shader); draw.SetRasterizationState(rs_state); draw.SetDepthStencilState(depth_state); draw.SetBlendState(blend_state); - float vertex_r = float((color >> 16) & 0xFF) / 255.0f; - float vertex_g = float((color >> 8) & 0xFF) / 255.0f; - float vertex_b = float((color >> 0) & 0xFF) / 255.0f; - float vertex_a = float((color >> 24) & 0xFF) / 255.0f; - float vertex_z = 1.0f - (float(z & 0xFFFFFF) / 16777216.0f); - draw.DrawColoredQuad(target_rc.left, target_rc.top, target_rc.GetWidth(), target_rc.GetHeight(), - vertex_r, vertex_g, vertex_b, vertex_a, vertex_z); + clear_color_value.color.float32[0], clear_color_value.color.float32[1], + clear_color_value.color.float32[2], clear_color_value.color.float32[3], + clear_depth_value.depthStencil.depth); } void Renderer::ReinterpretPixelData(unsigned int convtype) @@ -952,10 +976,11 @@ void Renderer::OnSwapChainResized() void Renderer::BindEFBToStateTracker() { // Update framebuffer in state tracker - VkRect2D framebuffer_render_area = { + VkRect2D framebuffer_size = { {0, 0}, {m_framebuffer_mgr->GetEFBWidth(), m_framebuffer_mgr->GetEFBHeight()}}; - m_state_tracker->SetRenderPass(m_framebuffer_mgr->GetEFBRenderPass()); - m_state_tracker->SetFramebuffer(m_framebuffer_mgr->GetEFBFramebuffer(), framebuffer_render_area); + m_state_tracker->SetRenderPass(m_framebuffer_mgr->GetEFBLoadRenderPass(), + m_framebuffer_mgr->GetEFBClearRenderPass()); + m_state_tracker->SetFramebuffer(m_framebuffer_mgr->GetEFBFramebuffer(), framebuffer_size); // Update rasterization state with MSAA info RasterizationState rs_state = {}; diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp index 2bb17a78dc..a1c4e5fb25 100644 --- a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp +++ b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp @@ -111,24 +111,28 @@ void StateTracker::SetIndexBuffer(VkBuffer buffer, VkDeviceSize offset, VkIndexT m_dirty_flags |= DIRTY_FLAG_INDEX_BUFFER; } -void StateTracker::SetRenderPass(VkRenderPass render_pass) +void StateTracker::SetRenderPass(VkRenderPass load_render_pass, VkRenderPass clear_render_pass) { // Should not be changed within a render pass. - assert(!m_in_render_pass); + _assert_(!InRenderPass()); - if (m_pipeline_state.render_pass == render_pass) - return; + // The clear and load render passes are compatible, so we don't need to change our pipeline. + if (m_pipeline_state.render_pass != load_render_pass) + { + m_pipeline_state.render_pass = load_render_pass; + m_dirty_flags |= DIRTY_FLAG_PIPELINE; + } - m_pipeline_state.render_pass = render_pass; - m_dirty_flags |= DIRTY_FLAG_PIPELINE; + m_load_render_pass = load_render_pass; + m_clear_render_pass = clear_render_pass; } void StateTracker::SetFramebuffer(VkFramebuffer framebuffer, const VkRect2D& render_area) { // Should not be changed within a render pass. - assert(!m_in_render_pass); + _assert_(!InRenderPass()); m_framebuffer = framebuffer; - m_framebuffer_render_area = render_area; + m_framebuffer_size = render_area; } void StateTracker::SetVertexFormat(const VertexFormat* vertex_format) @@ -508,12 +512,15 @@ void StateTracker::SetPendingRebind() void StateTracker::BeginRenderPass() { - if (m_in_render_pass) + if (InRenderPass()) return; + m_current_render_pass = m_load_render_pass; + m_framebuffer_render_area = m_framebuffer_size; + VkRenderPassBeginInfo begin_info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr, - m_pipeline_state.render_pass, + m_current_render_pass, m_framebuffer, m_framebuffer_render_area, 0, @@ -521,17 +528,34 @@ void StateTracker::BeginRenderPass() vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &begin_info, VK_SUBPASS_CONTENTS_INLINE); - - m_in_render_pass = true; } void StateTracker::EndRenderPass() { - if (!m_in_render_pass) + if (!InRenderPass()) return; vkCmdEndRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer()); - m_in_render_pass = false; + m_current_render_pass = nullptr; +} + +void StateTracker::BeginClearRenderPass(const VkRect2D& area, const VkClearValue clear_values[2]) +{ + _assert_(!InRenderPass()); + + m_current_render_pass = m_clear_render_pass; + m_framebuffer_render_area = area; + + VkRenderPassBeginInfo begin_info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + nullptr, + m_current_render_pass, + m_framebuffer, + m_framebuffer_render_area, + 2, + clear_values}; + + vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &begin_info, + VK_SUBPASS_CONTENTS_INLINE); } void StateTracker::SetViewport(const VkViewport& viewport) @@ -554,6 +578,10 @@ void StateTracker::SetScissor(const VkRect2D& scissor) bool StateTracker::Bind(bool rebind_all /*= false*/) { + // Check the render area if we were in a clear pass. + if (m_current_render_pass == m_clear_render_pass && !IsViewportWithinRenderArea()) + EndRenderPass(); + // Get new pipeline object if any parts have changed if (m_dirty_flags & DIRTY_FLAG_PIPELINE && !UpdatePipeline()) { @@ -580,7 +608,7 @@ bool StateTracker::Bind(bool rebind_all /*= false*/) } // Start render pass if not already started - if (!m_in_render_pass) + if (!InRenderPass()) BeginRenderPass(); // Re-bind parts of the pipeline @@ -707,6 +735,38 @@ void StateTracker::SetBackgroundCommandBufferExecution(bool enabled) m_allow_background_execution = enabled; } +bool StateTracker::IsWithinRenderArea(s32 x, s32 y, u32 width, u32 height) const +{ + // Check that the viewport does not lie outside the render area. + // If it does, we need to switch to a normal load/store render pass. + s32 left = m_framebuffer_render_area.offset.x; + s32 top = m_framebuffer_render_area.offset.y; + s32 right = left + static_cast(m_framebuffer_render_area.extent.width); + s32 bottom = top + static_cast(m_framebuffer_render_area.extent.height); + s32 test_left = x; + s32 test_top = y; + s32 test_right = test_left + static_cast(width); + s32 test_bottom = test_top + static_cast(height); + return test_left >= left && test_right <= right && test_top >= top && test_bottom <= bottom; +} + +bool StateTracker::IsViewportWithinRenderArea() const +{ + return IsWithinRenderArea(static_cast(m_viewport.x), static_cast(m_viewport.y), + static_cast(m_viewport.width), + static_cast(m_viewport.height)); +} + +void StateTracker::EndClearRenderPass() +{ + if (m_current_render_pass != m_clear_render_pass) + return; + + // End clear render pass. Bind() will call BeginRenderPass() which + // will switch to the load/store render pass. + EndRenderPass(); +} + bool StateTracker::UpdatePipeline() { // We need at least a vertex and fragment shader diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.h b/Source/Core/VideoBackends/Vulkan/StateTracker.h index 39b6d85fd3..2b807306e4 100644 --- a/Source/Core/VideoBackends/Vulkan/StateTracker.h +++ b/Source/Core/VideoBackends/Vulkan/StateTracker.h @@ -35,7 +35,8 @@ public: void SetVertexBuffer(VkBuffer buffer, VkDeviceSize offset); void SetIndexBuffer(VkBuffer buffer, VkDeviceSize offset, VkIndexType type); - void SetRenderPass(VkRenderPass render_pass); + void SetRenderPass(VkRenderPass load_render_pass, VkRenderPass clear_render_pass); + void SetFramebuffer(VkFramebuffer framebuffer, const VkRect2D& render_area); void SetVertexFormat(const VertexFormat* vertex_format); @@ -76,6 +77,10 @@ public: void BeginRenderPass(); void EndRenderPass(); + // Ends the current render pass if it was a clear render pass. + void BeginClearRenderPass(const VkRect2D& area, const VkClearValue clear_values[2]); + void EndClearRenderPass(); + void SetViewport(const VkViewport& viewport); void SetScissor(const VkRect2D& scissor); @@ -96,7 +101,12 @@ public: // Use when queries are active. void SetBackgroundCommandBufferExecution(bool enabled); + bool IsWithinRenderArea(s32 x, s32 y, u32 width, u32 height) const; + private: + // 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; bool UpdatePipeline(); bool UpdateDescriptorSet(); void UploadAllConstants(); @@ -162,8 +172,11 @@ private: std::unique_ptr m_uniform_stream_buffer; VkFramebuffer m_framebuffer = VK_NULL_HANDLE; + VkRenderPass m_load_render_pass = VK_NULL_HANDLE; + VkRenderPass m_clear_render_pass = VK_NULL_HANDLE; + VkRenderPass m_current_render_pass = VK_NULL_HANDLE; + VkRect2D m_framebuffer_size = {}; VkRect2D m_framebuffer_render_area = {}; - bool m_in_render_pass = false; bool m_bbox_enabled = false; // CPU access tracking