summaryrefslogtreecommitdiff
path: root/Source/Core/VideoBackends/Vulkan/StateTracker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Core/VideoBackends/Vulkan/StateTracker.cpp')
-rw-r--r--Source/Core/VideoBackends/Vulkan/StateTracker.cpp904
1 files changed, 904 insertions, 0 deletions
diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp
new file mode 100644
index 0000000000..a1c4e5fb25
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp
@@ -0,0 +1,904 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "Common/Assert.h"
+
+#include "VideoBackends/Vulkan/CommandBufferManager.h"
+#include "VideoBackends/Vulkan/Constants.h"
+#include "VideoBackends/Vulkan/FramebufferManager.h"
+#include "VideoBackends/Vulkan/ObjectCache.h"
+#include "VideoBackends/Vulkan/StateTracker.h"
+#include "VideoBackends/Vulkan/StreamBuffer.h"
+#include "VideoBackends/Vulkan/Util.h"
+#include "VideoBackends/Vulkan/VulkanContext.h"
+
+#include "VideoCommon/GeometryShaderManager.h"
+#include "VideoCommon/PixelShaderManager.h"
+#include "VideoCommon/Statistics.h"
+#include "VideoCommon/VertexShaderManager.h"
+#include "VideoCommon/VideoConfig.h"
+
+namespace Vulkan
+{
+StateTracker::StateTracker()
+{
+ // 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;
+ m_pipeline_state.depth_stencil_state.test_enable = VK_TRUE;
+ m_pipeline_state.depth_stencil_state.write_enable = VK_TRUE;
+ m_pipeline_state.depth_stencil_state.compare_op = VK_COMPARE_OP_LESS;
+ m_pipeline_state.blend_state.blend_enable = VK_FALSE;
+ m_pipeline_state.blend_state.blend_op = VK_BLEND_OP_ADD;
+ m_pipeline_state.blend_state.src_blend = VK_BLEND_FACTOR_ONE;
+ m_pipeline_state.blend_state.dst_blend = VK_BLEND_FACTOR_ZERO;
+ m_pipeline_state.blend_state.alpha_blend_op = VK_BLEND_OP_ADD;
+ m_pipeline_state.blend_state.src_alpha_blend = VK_BLEND_FACTOR_ONE;
+ m_pipeline_state.blend_state.dst_alpha_blend = VK_BLEND_FACTOR_ZERO;
+ m_pipeline_state.blend_state.logic_op_enable = VK_FALSE;
+ m_pipeline_state.blend_state.logic_op = VK_LOGIC_OP_CLEAR;
+ m_pipeline_state.blend_state.write_mask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+
+ // Enable depth clamping if supported by driver.
+ if (g_ActiveConfig.backend_info.bSupportsDepthClamp)
+ m_pipeline_state.rasterization_state.depth_clamp = VK_TRUE;
+
+ // BBox is disabled by default.
+ m_pipeline_state.pipeline_layout = g_object_cache->GetStandardPipelineLayout();
+ m_num_active_descriptor_sets = NUM_DESCRIPTOR_SETS - 1;
+ m_bbox_enabled = false;
+
+ // Initialize all samplers to point by default
+ for (size_t i = 0; i < NUM_PIXEL_SHADER_SAMPLERS; i++)
+ {
+ m_bindings.ps_samplers[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ m_bindings.ps_samplers[i].imageView = VK_NULL_HANDLE;
+ m_bindings.ps_samplers[i].sampler = g_object_cache->GetPointSampler();
+ }
+
+ // Create the streaming uniform buffer
+ m_uniform_stream_buffer =
+ 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");
+
+ // 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
+ // as many bytes as we use.
+ m_uniform_buffer_reserve_size = sizeof(PixelShaderConstants);
+ m_uniform_buffer_reserve_size = Util::AlignValue(m_uniform_buffer_reserve_size,
+ g_vulkan_context->GetUniformBufferAlignment()) +
+ sizeof(VertexShaderConstants);
+ m_uniform_buffer_reserve_size = Util::AlignValue(m_uniform_buffer_reserve_size,
+ g_vulkan_context->GetUniformBufferAlignment()) +
+ sizeof(GeometryShaderConstants);
+
+ // Default dirty flags include all descriptors
+ InvalidateDescriptorSets();
+ SetPendingRebind();
+
+ // Set default constants
+ UploadAllConstants();
+}
+
+StateTracker::~StateTracker()
+{
+}
+
+void StateTracker::SetVertexBuffer(VkBuffer buffer, VkDeviceSize offset)
+{
+ if (m_vertex_buffer == buffer && m_vertex_buffer_offset == offset)
+ return;
+
+ m_vertex_buffer = buffer;
+ m_vertex_buffer_offset = offset;
+ m_dirty_flags |= DIRTY_FLAG_VERTEX_BUFFER;
+}
+
+void StateTracker::SetIndexBuffer(VkBuffer buffer, VkDeviceSize offset, VkIndexType type)
+{
+ if (m_index_buffer == buffer && m_index_buffer_offset == offset && m_index_type == type)
+ return;
+
+ m_index_buffer = buffer;
+ m_index_buffer_offset = offset;
+ m_index_type = type;
+ m_dirty_flags |= DIRTY_FLAG_INDEX_BUFFER;
+}
+
+void StateTracker::SetRenderPass(VkRenderPass load_render_pass, VkRenderPass clear_render_pass)
+{
+ // Should not be changed within a render pass.
+ _assert_(!InRenderPass());
+
+ // 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_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_(!InRenderPass());
+ m_framebuffer = framebuffer;
+ m_framebuffer_size = render_area;
+}
+
+void StateTracker::SetVertexFormat(const VertexFormat* vertex_format)
+{
+ if (m_pipeline_state.vertex_format == vertex_format)
+ return;
+
+ m_pipeline_state.vertex_format = vertex_format;
+ m_dirty_flags |= DIRTY_FLAG_PIPELINE;
+}
+
+void StateTracker::SetPrimitiveTopology(VkPrimitiveTopology primitive_topology)
+{
+ if (m_pipeline_state.primitive_topology == primitive_topology)
+ return;
+
+ m_pipeline_state.primitive_topology = primitive_topology;
+ m_dirty_flags |= DIRTY_FLAG_PIPELINE;
+}
+
+void StateTracker::DisableBackFaceCulling()
+{
+ if (m_pipeline_state.rasterization_state.cull_mode == VK_CULL_MODE_NONE)
+ return;
+
+ m_pipeline_state.rasterization_state.cull_mode = VK_CULL_MODE_NONE;
+ m_dirty_flags |= DIRTY_FLAG_PIPELINE;
+}
+
+void StateTracker::SetRasterizationState(const RasterizationState& state)
+{
+ if (m_pipeline_state.rasterization_state.bits == state.bits)
+ return;
+
+ m_pipeline_state.rasterization_state.bits = state.bits;
+ m_dirty_flags |= DIRTY_FLAG_PIPELINE;
+}
+
+void StateTracker::SetDepthStencilState(const DepthStencilState& state)
+{
+ if (m_pipeline_state.depth_stencil_state.bits == state.bits)
+ return;
+
+ m_pipeline_state.depth_stencil_state.bits = state.bits;
+ m_dirty_flags |= DIRTY_FLAG_PIPELINE;
+}
+
+void StateTracker::SetBlendState(const BlendState& state)
+{
+ if (m_pipeline_state.blend_state.bits == state.bits)
+ return;
+
+ m_pipeline_state.blend_state.bits = state.bits;
+ m_dirty_flags |= DIRTY_FLAG_PIPELINE;
+}
+
+bool StateTracker::CheckForShaderChanges(u32 gx_primitive_type, DSTALPHA_MODE dstalpha_mode)
+{
+ VertexShaderUid vs_uid = GetVertexShaderUid();
+ PixelShaderUid ps_uid = GetPixelShaderUid(dstalpha_mode);
+
+ bool changed = false;
+
+ if (vs_uid != m_vs_uid)
+ {
+ m_pipeline_state.vs = g_object_cache->GetVertexShaderForUid(vs_uid);
+ m_vs_uid = vs_uid;
+ changed = true;
+ }
+
+ if (g_vulkan_context->SupportsGeometryShaders())
+ {
+ GeometryShaderUid gs_uid = GetGeometryShaderUid(gx_primitive_type);
+ if (gs_uid != m_gs_uid)
+ {
+ if (gs_uid.GetUidData()->IsPassthrough())
+ m_pipeline_state.gs = VK_NULL_HANDLE;
+ else
+ m_pipeline_state.gs = g_object_cache->GetGeometryShaderForUid(gs_uid);
+
+ m_gs_uid = gs_uid;
+ changed = true;
+ }
+ }
+
+ if (ps_uid != m_ps_uid)
+ {
+ m_pipeline_state.ps = g_object_cache->GetPixelShaderForUid(ps_uid, dstalpha_mode);
+ m_ps_uid = ps_uid;
+ changed = true;
+ }
+
+ if (m_dstalpha_mode != dstalpha_mode)
+ {
+ // Switching to/from alpha pass requires a pipeline change, since the blend state
+ // is overridden in the destination alpha pass.
+ if (m_dstalpha_mode == DSTALPHA_ALPHA_PASS || dstalpha_mode == DSTALPHA_ALPHA_PASS)
+ changed = true;
+
+ m_dstalpha_mode = dstalpha_mode;
+ }
+
+ if (changed)
+ m_dirty_flags |= DIRTY_FLAG_PIPELINE;
+
+ return changed;
+}
+
+void StateTracker::UpdateVertexShaderConstants()
+{
+ if (!VertexShaderManager::dirty)
+ return;
+
+ // Since the other stages uniform buffers' may be still be using the earlier data,
+ // we can't reuse the earlier part of the buffer without re-uploading everything.
+ if (!m_uniform_stream_buffer->ReserveMemory(m_uniform_buffer_reserve_size,
+ g_vulkan_context->GetUniformBufferAlignment(), false,
+ false, false))
+ {
+ // Re-upload all constants to a new portion of the buffer.
+ UploadAllConstants();
+ return;
+ }
+
+ // Buffer allocation changed?
+ if (m_uniform_stream_buffer->GetBuffer() !=
+ m_bindings.uniform_buffer_bindings[UBO_DESCRIPTOR_SET_BINDING_VS].buffer)
+ {
+ m_bindings.uniform_buffer_bindings[UBO_DESCRIPTOR_SET_BINDING_VS].buffer =
+ m_uniform_stream_buffer->GetBuffer();
+ m_dirty_flags |= DIRTY_FLAG_VS_UBO;
+ }
+
+ m_bindings.uniform_buffer_offsets[UBO_DESCRIPTOR_SET_BINDING_VS] =
+ static_cast<uint32_t>(m_uniform_stream_buffer->GetCurrentOffset());
+ m_dirty_flags |= DIRTY_FLAG_DYNAMIC_OFFSETS;
+
+ memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), &VertexShaderManager::constants,
+ sizeof(VertexShaderConstants));
+ ADDSTAT(stats.thisFrame.bytesUniformStreamed, sizeof(VertexShaderConstants));
+ m_uniform_stream_buffer->CommitMemory(sizeof(VertexShaderConstants));
+ VertexShaderManager::dirty = false;
+}
+
+void StateTracker::UpdateGeometryShaderConstants()
+{
+ // Skip updating geometry shader constants if it's not in use.
+ if (m_pipeline_state.gs == VK_NULL_HANDLE || !GeometryShaderManager::dirty)
+ return;
+
+ // Since the other stages uniform buffers' may be still be using the earlier data,
+ // we can't reuse the earlier part of the buffer without re-uploading everything.
+ if (!m_uniform_stream_buffer->ReserveMemory(m_uniform_buffer_reserve_size,
+ g_vulkan_context->GetUniformBufferAlignment(), false,
+ false, false))
+ {
+ // Re-upload all constants to a new portion of the buffer.
+ UploadAllConstants();
+ return;
+ }
+
+ // Buffer allocation changed?
+ if (m_uniform_stream_buffer->GetBuffer() !=
+ m_bindings.uniform_buffer_bindings[UBO_DESCRIPTOR_SET_BINDING_GS].buffer)
+ {
+ m_bindings.uniform_buffer_bindings[UBO_DESCRIPTOR_SET_BINDING_GS].buffer =
+ m_uniform_stream_buffer->GetBuffer();
+ m_dirty_flags |= DIRTY_FLAG_GS_UBO;
+ }
+
+ m_bindings.uniform_buffer_offsets[UBO_DESCRIPTOR_SET_BINDING_GS] =
+ static_cast<uint32_t>(m_uniform_stream_buffer->GetCurrentOffset());
+ m_dirty_flags |= DIRTY_FLAG_DYNAMIC_OFFSETS;
+
+ memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), &GeometryShaderManager::constants,
+ sizeof(GeometryShaderConstants));
+ ADDSTAT(stats.thisFrame.bytesUniformStreamed, sizeof(GeometryShaderConstants));
+ m_uniform_stream_buffer->CommitMemory(sizeof(GeometryShaderConstants));
+ GeometryShaderManager::dirty = false;
+}
+
+void StateTracker::UpdatePixelShaderConstants()
+{
+ if (!PixelShaderManager::dirty)
+ return;
+
+ // Since the other stages uniform buffers' may be still be using the earlier data,
+ // we can't reuse the earlier part of the buffer without re-uploading everything.
+ if (!m_uniform_stream_buffer->ReserveMemory(m_uniform_buffer_reserve_size,
+ g_vulkan_context->GetUniformBufferAlignment(), false,
+ false, false))
+ {
+ // Re-upload all constants to a new portion of the buffer.
+ UploadAllConstants();
+ return;
+ }
+
+ // Buffer allocation changed?
+ if (m_uniform_stream_buffer->GetBuffer() !=
+ m_bindings.uniform_buffer_bindings[UBO_DESCRIPTOR_SET_BINDING_PS].buffer)
+ {
+ m_bindings.uniform_buffer_bindings[UBO_DESCRIPTOR_SET_BINDING_PS].buffer =
+ m_uniform_stream_buffer->GetBuffer();
+ m_dirty_flags |= DIRTY_FLAG_PS_UBO;
+ }
+
+ m_bindings.uniform_buffer_offsets[UBO_DESCRIPTOR_SET_BINDING_PS] =
+ static_cast<uint32_t>(m_uniform_stream_buffer->GetCurrentOffset());
+ m_dirty_flags |= DIRTY_FLAG_DYNAMIC_OFFSETS;
+
+ memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), &PixelShaderManager::constants,
+ sizeof(PixelShaderConstants));
+ ADDSTAT(stats.thisFrame.bytesUniformStreamed, sizeof(PixelShaderConstants));
+ m_uniform_stream_buffer->CommitMemory(sizeof(PixelShaderConstants));
+ PixelShaderManager::dirty = false;
+}
+
+void StateTracker::UploadAllConstants()
+{
+ // We are free to re-use parts of the buffer now since we're uploading all constants.
+ size_t pixel_constants_offset = 0;
+ size_t vertex_constants_offset =
+ Util::AlignValue(pixel_constants_offset + sizeof(PixelShaderConstants),
+ g_vulkan_context->GetUniformBufferAlignment());
+ size_t geometry_constants_offset =
+ Util::AlignValue(vertex_constants_offset + sizeof(VertexShaderConstants),
+ g_vulkan_context->GetUniformBufferAlignment());
+ size_t total_allocation_size = geometry_constants_offset + sizeof(GeometryShaderConstants);
+
+ // Allocate everything at once.
+ if (!m_uniform_stream_buffer->ReserveMemory(
+ total_allocation_size, g_vulkan_context->GetUniformBufferAlignment(), true, true, false))
+ {
+ // 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);
+ if (!m_uniform_stream_buffer->ReserveMemory(total_allocation_size,
+ g_vulkan_context->GetUniformBufferAlignment(), true,
+ true, false))
+ {
+ PanicAlert("Failed to allocate space for constants in streaming buffer");
+ return;
+ }
+ }
+
+ // Update bindings
+ for (size_t i = 0; i < NUM_UBO_DESCRIPTOR_SET_BINDINGS; i++)
+ {
+ m_bindings.uniform_buffer_bindings[i].buffer = m_uniform_stream_buffer->GetBuffer();
+ m_bindings.uniform_buffer_bindings[i].offset = 0;
+ }
+ m_bindings.uniform_buffer_bindings[UBO_DESCRIPTOR_SET_BINDING_PS].range =
+ sizeof(PixelShaderConstants);
+ m_bindings.uniform_buffer_bindings[UBO_DESCRIPTOR_SET_BINDING_VS].range =
+ sizeof(VertexShaderConstants);
+ m_bindings.uniform_buffer_bindings[UBO_DESCRIPTOR_SET_BINDING_GS].range =
+ sizeof(GeometryShaderConstants);
+
+ // Update dynamic offsets
+ m_bindings.uniform_buffer_offsets[UBO_DESCRIPTOR_SET_BINDING_PS] =
+ static_cast<uint32_t>(m_uniform_stream_buffer->GetCurrentOffset() + pixel_constants_offset);
+
+ m_bindings.uniform_buffer_offsets[UBO_DESCRIPTOR_SET_BINDING_VS] =
+ static_cast<uint32_t>(m_uniform_stream_buffer->GetCurrentOffset() + vertex_constants_offset);
+
+ m_bindings.uniform_buffer_offsets[UBO_DESCRIPTOR_SET_BINDING_GS] = static_cast<uint32_t>(
+ m_uniform_stream_buffer->GetCurrentOffset() + geometry_constants_offset);
+
+ m_dirty_flags |= DIRTY_FLAG_ALL_DESCRIPTOR_SETS | DIRTY_FLAG_DYNAMIC_OFFSETS | DIRTY_FLAG_VS_UBO |
+ DIRTY_FLAG_GS_UBO | DIRTY_FLAG_PS_UBO;
+
+ // Copy the actual data in
+ memcpy(m_uniform_stream_buffer->GetCurrentHostPointer() + pixel_constants_offset,
+ &PixelShaderManager::constants, sizeof(PixelShaderConstants));
+ memcpy(m_uniform_stream_buffer->GetCurrentHostPointer() + vertex_constants_offset,
+ &VertexShaderManager::constants, sizeof(VertexShaderConstants));
+ memcpy(m_uniform_stream_buffer->GetCurrentHostPointer() + geometry_constants_offset,
+ &GeometryShaderManager::constants, sizeof(GeometryShaderConstants));
+
+ // Finally, flush buffer memory after copying
+ m_uniform_stream_buffer->CommitMemory(total_allocation_size);
+
+ // Clear dirty flags
+ VertexShaderManager::dirty = false;
+ GeometryShaderManager::dirty = false;
+ PixelShaderManager::dirty = false;
+}
+
+void StateTracker::SetTexture(size_t index, VkImageView view)
+{
+ if (m_bindings.ps_samplers[index].imageView == view)
+ return;
+
+ m_bindings.ps_samplers[index].imageView = view;
+ m_bindings.ps_samplers[index].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ m_dirty_flags |= DIRTY_FLAG_PS_SAMPLERS;
+}
+
+void StateTracker::SetSampler(size_t index, VkSampler sampler)
+{
+ if (m_bindings.ps_samplers[index].sampler == sampler)
+ return;
+
+ m_bindings.ps_samplers[index].sampler = sampler;
+ m_dirty_flags |= DIRTY_FLAG_PS_SAMPLERS;
+}
+
+void StateTracker::SetBBoxEnable(bool enable)
+{
+ if (m_bbox_enabled == enable)
+ return;
+
+ // Change the number of active descriptor sets, as well as the pipeline layout
+ if (enable)
+ {
+ m_pipeline_state.pipeline_layout = g_object_cache->GetBBoxPipelineLayout();
+ m_num_active_descriptor_sets = NUM_DESCRIPTOR_SETS;
+
+ // The bbox buffer never changes, so we defer descriptor updates until it is enabled.
+ if (m_descriptor_sets[DESCRIPTOR_SET_SHADER_STORAGE_BUFFERS] == VK_NULL_HANDLE)
+ m_dirty_flags |= DIRTY_FLAG_PS_SSBO;
+ }
+ else
+ {
+ m_pipeline_state.pipeline_layout = g_object_cache->GetStandardPipelineLayout();
+ m_num_active_descriptor_sets = NUM_DESCRIPTOR_SETS - 1;
+ }
+
+ m_dirty_flags |= DIRTY_FLAG_PIPELINE | DIRTY_FLAG_DESCRIPTOR_SET_BINDING;
+ m_bbox_enabled = enable;
+}
+
+void StateTracker::SetBBoxBuffer(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range)
+{
+ if (m_bindings.ps_ssbo.buffer == buffer && m_bindings.ps_ssbo.offset == offset &&
+ m_bindings.ps_ssbo.range == range)
+ {
+ return;
+ }
+
+ m_bindings.ps_ssbo.buffer = buffer;
+ m_bindings.ps_ssbo.offset = offset;
+ m_bindings.ps_ssbo.range = range;
+
+ // Defer descriptor update until bbox is actually enabled.
+ if (m_bbox_enabled)
+ m_dirty_flags |= DIRTY_FLAG_PS_SSBO;
+}
+
+void StateTracker::UnbindTexture(VkImageView view)
+{
+ for (VkDescriptorImageInfo& it : m_bindings.ps_samplers)
+ {
+ if (it.imageView == view)
+ it.imageView = VK_NULL_HANDLE;
+ }
+}
+
+void StateTracker::InvalidateDescriptorSets()
+{
+ m_descriptor_sets.fill(VK_NULL_HANDLE);
+ m_dirty_flags |= DIRTY_FLAG_ALL_DESCRIPTOR_SETS;
+
+ // Defer SSBO descriptor update until bbox is actually enabled.
+ if (!m_bbox_enabled)
+ m_dirty_flags &= ~DIRTY_FLAG_PS_SSBO;
+}
+
+void StateTracker::SetPendingRebind()
+{
+ m_dirty_flags |= DIRTY_FLAG_DYNAMIC_OFFSETS | DIRTY_FLAG_DESCRIPTOR_SET_BINDING |
+ DIRTY_FLAG_PIPELINE_BINDING | DIRTY_FLAG_VERTEX_BUFFER |
+ DIRTY_FLAG_INDEX_BUFFER | DIRTY_FLAG_VIEWPORT | DIRTY_FLAG_SCISSOR |
+ DIRTY_FLAG_PIPELINE;
+}
+
+void StateTracker::BeginRenderPass()
+{
+ 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_current_render_pass,
+ m_framebuffer,
+ m_framebuffer_render_area,
+ 0,
+ nullptr};
+
+ vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &begin_info,
+ VK_SUBPASS_CONTENTS_INLINE);
+}
+
+void StateTracker::EndRenderPass()
+{
+ if (!InRenderPass())
+ return;
+
+ vkCmdEndRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer());
+ 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)
+{
+ if (memcmp(&m_viewport, &viewport, sizeof(viewport)) == 0)
+ return;
+
+ m_viewport = viewport;
+ m_dirty_flags |= DIRTY_FLAG_VIEWPORT;
+}
+
+void StateTracker::SetScissor(const VkRect2D& scissor)
+{
+ if (memcmp(&m_scissor, &scissor, sizeof(scissor)) == 0)
+ return;
+
+ m_scissor = scissor;
+ m_dirty_flags |= DIRTY_FLAG_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())
+ {
+ ERROR_LOG(VIDEO, "Failed to get pipeline object, skipping draw");
+ return false;
+ }
+
+ // Get a new descriptor set if any parts have changed
+ if (m_dirty_flags & DIRTY_FLAG_ALL_DESCRIPTOR_SETS && !UpdateDescriptorSet())
+ {
+ // We can fail to allocate descriptors if we exhaust the pool for this command buffer.
+ WARN_LOG(VIDEO, "Failed to get a descriptor set, executing buffer");
+
+ // Try again after executing the current buffer.
+ g_command_buffer_mgr->ExecuteCommandBuffer(false, false);
+ InvalidateDescriptorSets();
+ SetPendingRebind();
+ if (!UpdateDescriptorSet())
+ {
+ // Something strange going on.
+ ERROR_LOG(VIDEO, "Failed to get descriptor set, skipping draw");
+ return false;
+ }
+ }
+
+ // Start render pass if not already started
+ if (!InRenderPass())
+ BeginRenderPass();
+
+ // Re-bind parts of the pipeline
+ VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer();
+ if (m_dirty_flags & DIRTY_FLAG_VERTEX_BUFFER || rebind_all)
+ vkCmdBindVertexBuffers(command_buffer, 0, 1, &m_vertex_buffer, &m_vertex_buffer_offset);
+
+ if (m_dirty_flags & DIRTY_FLAG_INDEX_BUFFER || rebind_all)
+ vkCmdBindIndexBuffer(command_buffer, m_index_buffer, m_index_buffer_offset, m_index_type);
+
+ if (m_dirty_flags & DIRTY_FLAG_PIPELINE_BINDING || rebind_all)
+ vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_object);
+
+ if (m_dirty_flags & DIRTY_FLAG_DESCRIPTOR_SET_BINDING || rebind_all)
+ {
+ vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+ m_pipeline_state.pipeline_layout, 0, m_num_active_descriptor_sets,
+ m_descriptor_sets.data(), NUM_UBO_DESCRIPTOR_SET_BINDINGS,
+ m_bindings.uniform_buffer_offsets.data());
+ }
+ else if (m_dirty_flags & DIRTY_FLAG_DYNAMIC_OFFSETS)
+ {
+ vkCmdBindDescriptorSets(
+ command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_state.pipeline_layout,
+ DESCRIPTOR_SET_UNIFORM_BUFFERS, 1, &m_descriptor_sets[DESCRIPTOR_SET_UNIFORM_BUFFERS],
+ NUM_UBO_DESCRIPTOR_SET_BINDINGS, m_bindings.uniform_buffer_offsets.data());
+ }
+
+ if (m_dirty_flags & DIRTY_FLAG_VIEWPORT || rebind_all)
+ vkCmdSetViewport(command_buffer, 0, 1, &m_viewport);
+
+ if (m_dirty_flags & DIRTY_FLAG_SCISSOR || rebind_all)
+ vkCmdSetScissor(command_buffer, 0, 1, &m_scissor);
+
+ m_dirty_flags = 0;
+ return true;
+}
+
+void StateTracker::OnDraw()
+{
+ m_draw_counter++;
+
+ // If we didn't have any CPU access last frame, do nothing.
+ if (m_scheduled_command_buffer_kicks.empty() || !m_allow_background_execution)
+ return;
+
+ // Check if this draw is scheduled to kick a command buffer.
+ // The draw counters will always be sorted so a binary search is possible here.
+ if (std::binary_search(m_scheduled_command_buffer_kicks.begin(),
+ m_scheduled_command_buffer_kicks.end(), m_draw_counter))
+ {
+ // Kick a command buffer on the background thread.
+ EndRenderPass();
+ g_command_buffer_mgr->ExecuteCommandBuffer(true, false);
+ InvalidateDescriptorSets();
+ SetPendingRebind();
+ }
+}
+
+void StateTracker::OnReadback()
+{
+ // Check this isn't another access without any draws inbetween.
+ if (!m_cpu_accesses_this_frame.empty() && m_cpu_accesses_this_frame.back() == m_draw_counter)
+ return;
+
+ // Store the current draw counter for scheduling in OnEndFrame.
+ m_cpu_accesses_this_frame.emplace_back(m_draw_counter);
+}
+
+void StateTracker::OnEndFrame()
+{
+ m_draw_counter = 0;
+ m_scheduled_command_buffer_kicks.clear();
+
+ // If we have no CPU access at all, leave everything in the one command buffer for maximum
+ // parallelism between CPU/GPU, at the cost of slightly higher latency.
+ if (m_cpu_accesses_this_frame.empty())
+ return;
+
+ // In order to reduce CPU readback latency, we want to kick a command buffer roughly halfway
+ // between the draw counters that invoked the readback, or every 250 draws, whichever is smaller.
+ if (g_ActiveConfig.iCommandBufferExecuteInterval > 0)
+ {
+ u32 last_draw_counter = 0;
+ u32 interval = static_cast<u32>(g_ActiveConfig.iCommandBufferExecuteInterval);
+ for (u32 draw_counter : m_cpu_accesses_this_frame)
+ {
+ u32 draw_count = draw_counter - last_draw_counter;
+ if (draw_count <= interval)
+ {
+ u32 mid_point = draw_count / 2;
+ m_scheduled_command_buffer_kicks.emplace_back(last_draw_counter + mid_point);
+ }
+ else
+ {
+ u32 counter = interval;
+ while (counter < draw_count)
+ {
+ m_scheduled_command_buffer_kicks.emplace_back(last_draw_counter + counter);
+ counter += interval;
+ }
+ }
+ }
+ }
+
+#if 0
+ {
+ std::stringstream ss;
+ std::for_each(m_cpu_accesses_this_frame.begin(), m_cpu_accesses_this_frame.end(), [&ss](u32 idx) { ss << idx << ","; });
+ WARN_LOG(VIDEO, "CPU EFB accesses in last frame: %s", ss.str().c_str());
+ }
+ {
+ std::stringstream ss;
+ std::for_each(m_scheduled_command_buffer_kicks.begin(), m_scheduled_command_buffer_kicks.end(), [&ss](u32 idx) { ss << idx << ","; });
+ WARN_LOG(VIDEO, "Scheduled command buffer kicks: %s", ss.str().c_str());
+ }
+#endif
+
+ m_cpu_accesses_this_frame.clear();
+}
+
+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<s32>(m_framebuffer_render_area.extent.width);
+ s32 bottom = top + static_cast<s32>(m_framebuffer_render_area.extent.height);
+ s32 test_left = x;
+ s32 test_top = y;
+ s32 test_right = test_left + static_cast<s32>(width);
+ s32 test_bottom = test_top + static_cast<s32>(height);
+ return test_left >= left && test_right <= right && test_top >= top && test_bottom <= bottom;
+}
+
+bool StateTracker::IsViewportWithinRenderArea() const
+{
+ return IsWithinRenderArea(static_cast<s32>(m_viewport.x), static_cast<s32>(m_viewport.y),
+ static_cast<u32>(m_viewport.width),
+ static_cast<u32>(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
+ if (m_pipeline_state.vs == VK_NULL_HANDLE || m_pipeline_state.ps == VK_NULL_HANDLE)
+ return false;
+
+ // Grab a new pipeline object, this can fail
+ if (m_dstalpha_mode != DSTALPHA_ALPHA_PASS)
+ {
+ m_pipeline_object = g_object_cache->GetPipeline(m_pipeline_state);
+ if (m_pipeline_object == VK_NULL_HANDLE)
+ return false;
+ }
+ else
+ {
+ // We need to make a few modifications to the pipeline object, but retain
+ // the existing state, since we don't want to break the next draw.
+ PipelineInfo temp_info = m_pipeline_state;
+
+ // Skip depth writes for this pass. The results will be the same, so no
+ // point in overwriting depth values with the same value.
+ temp_info.depth_stencil_state.write_enable = VK_FALSE;
+
+ // Only allow alpha writes, and disable blending.
+ temp_info.blend_state.blend_enable = VK_FALSE;
+ temp_info.blend_state.logic_op_enable = VK_FALSE;
+ temp_info.blend_state.write_mask = VK_COLOR_COMPONENT_A_BIT;
+
+ m_pipeline_object = g_object_cache->GetPipeline(temp_info);
+ if (m_pipeline_object == VK_NULL_HANDLE)
+ return false;
+ }
+
+ m_dirty_flags |= DIRTY_FLAG_PIPELINE_BINDING;
+ return true;
+}
+
+bool StateTracker::UpdateDescriptorSet()
+{
+ const size_t MAX_DESCRIPTOR_WRITES = NUM_UBO_DESCRIPTOR_SET_BINDINGS + // UBO
+ NUM_PIXEL_SHADER_SAMPLERS + // Samplers
+ 1; // SSBO
+ std::array<VkWriteDescriptorSet, MAX_DESCRIPTOR_WRITES> writes;
+ u32 num_writes = 0;
+
+ if (m_dirty_flags & (DIRTY_FLAG_VS_UBO | DIRTY_FLAG_GS_UBO | DIRTY_FLAG_PS_UBO) ||
+ m_descriptor_sets[DESCRIPTOR_SET_UNIFORM_BUFFERS] == VK_NULL_HANDLE)
+ {
+ VkDescriptorSetLayout layout =
+ g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_UNIFORM_BUFFERS);
+ VkDescriptorSet set = g_command_buffer_mgr->AllocateDescriptorSet(layout);
+ if (set == VK_NULL_HANDLE)
+ return false;
+
+ for (size_t i = 0; i < NUM_UBO_DESCRIPTOR_SET_BINDINGS; i++)
+ {
+ writes[num_writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ nullptr,
+ set,
+ static_cast<uint32_t>(i),
+ 0,
+ 1,
+ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
+ nullptr,
+ &m_bindings.uniform_buffer_bindings[i],
+ nullptr};
+ }
+
+ m_descriptor_sets[DESCRIPTOR_SET_UNIFORM_BUFFERS] = set;
+ m_dirty_flags |= DIRTY_FLAG_DESCRIPTOR_SET_BINDING;
+ }
+
+ if (m_dirty_flags & DIRTY_FLAG_PS_SAMPLERS ||
+ m_descriptor_sets[DESCRIPTOR_SET_PIXEL_SHADER_SAMPLERS] == VK_NULL_HANDLE)
+ {
+ VkDescriptorSetLayout layout =
+ g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_PIXEL_SHADER_SAMPLERS);
+ VkDescriptorSet set = g_command_buffer_mgr->AllocateDescriptorSet(layout);
+ if (set == VK_NULL_HANDLE)
+ return false;
+
+ for (size_t i = 0; i < NUM_PIXEL_SHADER_SAMPLERS; i++)
+ {
+ const VkDescriptorImageInfo& info = m_bindings.ps_samplers[i];
+ if (info.imageView != VK_NULL_HANDLE && info.sampler != VK_NULL_HANDLE)
+ {
+ writes[num_writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ nullptr,
+ set,
+ static_cast<uint32_t>(i),
+ 0,
+ 1,
+ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ &info,
+ nullptr,
+ nullptr};
+ }
+ }
+
+ m_descriptor_sets[DESCRIPTOR_SET_PIXEL_SHADER_SAMPLERS] = set;
+ m_dirty_flags |= DIRTY_FLAG_DESCRIPTOR_SET_BINDING;
+ }
+
+ if (m_bbox_enabled &&
+ (m_dirty_flags & DIRTY_FLAG_PS_SSBO ||
+ m_descriptor_sets[DESCRIPTOR_SET_SHADER_STORAGE_BUFFERS] == VK_NULL_HANDLE))
+ {
+ VkDescriptorSetLayout layout =
+ g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_SHADER_STORAGE_BUFFERS);
+ VkDescriptorSet set = g_command_buffer_mgr->AllocateDescriptorSet(layout);
+ if (set == VK_NULL_HANDLE)
+ return false;
+
+ writes[num_writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ nullptr,
+ set,
+ 0,
+ 0,
+ 1,
+ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ nullptr,
+ &m_bindings.ps_ssbo,
+ nullptr};
+
+ m_descriptor_sets[DESCRIPTOR_SET_SHADER_STORAGE_BUFFERS] = set;
+ m_dirty_flags |= DIRTY_FLAG_DESCRIPTOR_SET_BINDING;
+ }
+
+ if (num_writes > 0)
+ vkUpdateDescriptorSets(g_vulkan_context->GetDevice(), num_writes, writes.data(), 0, nullptr);
+
+ return true;
+}
+
+} // namespace Vulkan