mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 06:09:50 -06:00
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:
@ -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()
|
||||
|
Reference in New Issue
Block a user