Vulkan: Implement a pipeline UID cache

This stores enough information to recreate the pipeline, including the
shader UIDs, blend/depth/rasterization state, primitive and vertex format.
This commit is contained in:
Stenzek
2016-11-13 18:39:06 +10:00
parent 681294586b
commit aac66a1b61
7 changed files with 227 additions and 40 deletions

View File

@ -14,6 +14,7 @@
#include "VideoBackends/Vulkan/ObjectCache.h"
#include "VideoBackends/Vulkan/StreamBuffer.h"
#include "VideoBackends/Vulkan/Util.h"
#include "VideoBackends/Vulkan/VertexFormat.h"
#include "VideoBackends/Vulkan/VulkanContext.h"
#include "VideoCommon/GeometryShaderManager.h"
@ -116,6 +117,93 @@ bool StateTracker::Initialize()
return true;
}
void StateTracker::LoadPipelineUIDCache()
{
class PipelineInserter final : public LinearDiskCacheReader<SerializedPipelineUID, u32>
{
public:
explicit PipelineInserter(StateTracker* this_ptr_) : this_ptr(this_ptr_) {}
void Read(const SerializedPipelineUID& key, const u32* value, u32 value_size)
{
this_ptr->PrecachePipelineUID(key);
}
private:
StateTracker* this_ptr;
};
std::string filename = g_object_cache->GetDiskCacheFileName("pipeline-uid");
PipelineInserter inserter(this);
// OpenAndRead calls Close() first, which will flush all data to disk when reloading.
// This assertion must hold true, otherwise data corruption will result.
m_uid_cache.OpenAndRead(filename, inserter);
}
void StateTracker::AppendToPipelineUIDCache(const PipelineInfo& info)
{
SerializedPipelineUID sinfo;
sinfo.blend_state_bits = info.blend_state.bits;
sinfo.rasterizer_state_bits = info.rasterization_state.bits;
sinfo.depth_stencil_state_bits = info.depth_stencil_state.bits;
sinfo.vertex_decl = m_pipeline_state.vertex_format->GetVertexDeclaration();
sinfo.vs_uid = m_vs_uid;
sinfo.gs_uid = m_gs_uid;
sinfo.ps_uid = m_ps_uid;
sinfo.primitive_topology = info.primitive_topology;
u32 dummy_value = 0;
m_uid_cache.Append(sinfo, &dummy_value, 1);
}
bool StateTracker::PrecachePipelineUID(const SerializedPipelineUID& uid)
{
PipelineInfo pinfo = {};
// Need to create the vertex declaration first, rather than deferring to when a game creates a
// vertex loader that uses this format, since we need it to create a pipeline.
pinfo.vertex_format = VertexFormat::GetOrCreateMatchingFormat(uid.vertex_decl);
pinfo.pipeline_layout = uid.ps_uid.GetUidData()->bounding_box ?
g_object_cache->GetBBoxPipelineLayout() :
g_object_cache->GetStandardPipelineLayout();
pinfo.vs = g_object_cache->GetVertexShaderForUid(uid.vs_uid);
if (pinfo.vs == VK_NULL_HANDLE)
{
WARN_LOG(VIDEO, "Failed to get vertex shader from cached UID.");
return false;
}
if (!uid.gs_uid.GetUidData()->IsPassthrough())
{
pinfo.gs = g_object_cache->GetGeometryShaderForUid(uid.gs_uid);
if (pinfo.gs == VK_NULL_HANDLE)
{
WARN_LOG(VIDEO, "Failed to get geometry shader from cached UID.");
return false;
}
}
pinfo.ps = g_object_cache->GetPixelShaderForUid(uid.ps_uid);
if (pinfo.ps == VK_NULL_HANDLE)
{
WARN_LOG(VIDEO, "Failed to get pixel shader from cached UID.");
return false;
}
pinfo.render_pass = m_load_render_pass;
pinfo.blend_state.bits = uid.blend_state_bits;
pinfo.rasterization_state.bits = uid.rasterizer_state_bits;
pinfo.depth_stencil_state.bits = uid.depth_stencil_state_bits;
pinfo.primitive_topology = uid.primitive_topology;
VkPipeline pipeline = g_object_cache->GetPipeline(pinfo);
if (pipeline == VK_NULL_HANDLE)
{
WARN_LOG(VIDEO, "Failed to get pipeline from cached UID.");
return false;
}
// We don't need to do anything with this pipeline, just make sure it exists.
return true;
}
void StateTracker::SetVertexBuffer(VkBuffer buffer, VkDeviceSize offset)
{
if (m_vertex_buffer == buffer && m_vertex_buffer_offset == offset)
@ -793,41 +881,54 @@ void StateTracker::EndClearRenderPass()
EndRenderPass();
}
PipelineInfo StateTracker::GetAlphaPassPipelineConfig(const PipelineInfo& info) const
{
PipelineInfo temp_info = info;
// 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;
return temp_info;
}
VkPipeline StateTracker::GetPipelineAndCacheUID(const PipelineInfo& info)
{
auto result = g_object_cache->GetPipelineWithCacheResult(info);
// Add to the UID cache if it is a new pipeline.
if (!result.second)
AppendToPipelineUIDCache(info);
return result.first;
}
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)
// Grab a new pipeline object, this can fail.
// We have to use a different blend state for the alpha pass of the dstalpha fallback.
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;
// We need to retain the existing state, since we don't want to break the next draw.
PipelineInfo temp_info = GetAlphaPassPipelineConfig(m_pipeline_state);
m_pipeline_object = GetPipelineAndCacheUID(temp_info);
}
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_pipeline_object = GetPipelineAndCacheUID(m_pipeline_state);
}
m_dirty_flags |= DIRTY_FLAG_PIPELINE_BINDING;
return true;
return m_pipeline_object != VK_NULL_HANDLE;
}
bool StateTracker::UpdateDescriptorSet()