summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorStenzek <stenzek@users.noreply.github.com>2016-11-28 22:03:49 +1000
committerGitHub <noreply@github.com>2016-11-28 22:03:49 +1000
commitda87580dc1c8eb63dac783f0e50e9bd3cfe4b3a1 (patch)
tree3875647817c8ef5e0f8843f02bba13fc1f12e91d /Source/Core
parent681294586b3267f12d4c1632d9ff07e1298b58a6 (diff)
parent8d48319414d7e3f348e4ef38e976121e9a1fce6a (diff)
Merge pull request #4449 from stenzek/vulkan-pipeline-cache
Vulkan: Implement pipeline UID cache
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/VideoBackends/Vulkan/ObjectCache.cpp114
-rw-r--r--Source/Core/VideoBackends/Vulkan/ObjectCache.h19
-rw-r--r--Source/Core/VideoBackends/Vulkan/Renderer.cpp5
-rw-r--r--Source/Core/VideoBackends/Vulkan/StateTracker.cpp145
-rw-r--r--Source/Core/VideoBackends/Vulkan/StateTracker.h56
-rw-r--r--Source/Core/VideoBackends/Vulkan/VertexFormat.cpp13
-rw-r--r--Source/Core/VideoBackends/Vulkan/VertexFormat.h5
7 files changed, 304 insertions, 53 deletions
diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
index 61e685874a..761402e478 100644
--- a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
+++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
@@ -159,12 +159,8 @@ GetVulkanColorBlendState(const BlendState& state,
return vk_state;
}
-VkPipeline ObjectCache::GetPipeline(const PipelineInfo& info)
+VkPipeline ObjectCache::CreatePipeline(const PipelineInfo& info)
{
- auto iter = m_pipeline_objects.find(info);
- if (iter != m_pipeline_objects.end())
- return iter->second;
-
// Declare descriptors for empty vertex buffers/attributes
static const VkPipelineVertexInputStateCreateInfo empty_vertex_input_state = {
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // VkStructureType sType
@@ -278,16 +274,34 @@ VkPipeline ObjectCache::GetPipeline(const PipelineInfo& info)
-1 // int32_t basePipelineIndex
};
- VkPipeline pipeline = VK_NULL_HANDLE;
+ VkPipeline pipeline;
VkResult res = vkCreateGraphicsPipelines(g_vulkan_context->GetDevice(), m_pipeline_cache, 1,
&pipeline_info, nullptr, &pipeline);
if (res != VK_SUCCESS)
+ {
LOG_VULKAN_ERROR(res, "vkCreateGraphicsPipelines failed: ");
+ return VK_NULL_HANDLE;
+ }
- m_pipeline_objects.emplace(info, pipeline);
return pipeline;
}
+VkPipeline ObjectCache::GetPipeline(const PipelineInfo& info)
+{
+ return GetPipelineWithCacheResult(info).first;
+}
+
+std::pair<VkPipeline, bool> ObjectCache::GetPipelineWithCacheResult(const PipelineInfo& info)
+{
+ auto iter = m_pipeline_objects.find(info);
+ if (iter != m_pipeline_objects.end())
+ return {iter->second, true};
+
+ VkPipeline pipeline = CreatePipeline(info);
+ m_pipeline_objects.emplace(info, pipeline);
+ return {pipeline, false};
+}
+
std::string ObjectCache::GetDiskCacheFileName(const char* type)
{
return StringFromFormat("%svulkan-%s-%s.cache", File::GetUserPath(D_SHADERCACHE_IDX).c_str(),
@@ -330,6 +344,13 @@ bool ObjectCache::CreatePipelineCache(bool load_from_disk)
disk_data.clear();
}
+ if (!disk_data.empty() && !ValidatePipelineCache(disk_data.data(), disk_data.size()))
+ {
+ // Don't use this data. In fact, we should delete it to prevent it from being used next time.
+ File::Delete(m_pipeline_cache_filename);
+ disk_data.clear();
+ }
+
VkPipelineCacheCreateInfo info = {
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
nullptr, // const void* pNext
@@ -355,6 +376,76 @@ bool ObjectCache::CreatePipelineCache(bool load_from_disk)
return false;
}
+// Based on Vulkan 1.0 specification,
+// Table 9.1. Layout for pipeline cache header version VK_PIPELINE_CACHE_HEADER_VERSION_ONE
+// NOTE: This data is assumed to be in little-endian format.
+#pragma pack(push, 4)
+struct VK_PIPELINE_CACHE_HEADER
+{
+ u32 header_length;
+ u32 header_version;
+ u32 vendor_id;
+ u32 device_id;
+ u8 uuid[VK_UUID_SIZE];
+};
+#pragma pack(pop)
+// TODO: Remove the #if here when GCC 5 is a minimum build requirement.
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5
+static_assert(std::has_trivial_copy_constructor<VK_PIPELINE_CACHE_HEADER>::value,
+ "VK_PIPELINE_CACHE_HEADER must be trivially copyable");
+#else
+static_assert(std::is_trivially_copyable<VK_PIPELINE_CACHE_HEADER>::value,
+ "VK_PIPELINE_CACHE_HEADER must be trivially copyable");
+#endif
+
+bool ObjectCache::ValidatePipelineCache(const u8* data, size_t data_length)
+{
+ if (data_length < sizeof(VK_PIPELINE_CACHE_HEADER))
+ {
+ ERROR_LOG(VIDEO, "Pipeline cache failed validation: Invalid header");
+ return false;
+ }
+
+ VK_PIPELINE_CACHE_HEADER header;
+ std::memcpy(&header, data, sizeof(header));
+ if (header.header_length < sizeof(VK_PIPELINE_CACHE_HEADER))
+ {
+ ERROR_LOG(VIDEO, "Pipeline cache failed validation: Invalid header length");
+ return false;
+ }
+
+ if (header.header_version != VK_PIPELINE_CACHE_HEADER_VERSION_ONE)
+ {
+ ERROR_LOG(VIDEO, "Pipeline cache failed validation: Invalid header version");
+ return false;
+ }
+
+ if (header.vendor_id != g_vulkan_context->GetDeviceProperties().vendorID)
+ {
+ ERROR_LOG(VIDEO,
+ "Pipeline cache failed validation: Incorrect vendor ID (file: 0x%X, device: 0x%X)",
+ header.vendor_id, g_vulkan_context->GetDeviceProperties().vendorID);
+ return false;
+ }
+
+ if (header.device_id != g_vulkan_context->GetDeviceProperties().deviceID)
+ {
+ ERROR_LOG(VIDEO,
+ "Pipeline cache failed validation: Incorrect device ID (file: 0x%X, device: 0x%X)",
+ header.device_id, g_vulkan_context->GetDeviceProperties().deviceID);
+ return false;
+ }
+
+ if (std::memcmp(header.uuid, g_vulkan_context->GetDeviceProperties().pipelineCacheUUID,
+ VK_UUID_SIZE) != 0)
+ {
+ ERROR_LOG(VIDEO, "Pipeline cache failed validation: Incorrect UUID");
+ return false;
+ }
+
+ return true;
+}
+
void ObjectCache::DestroyPipelineCache()
{
for (const auto& it : m_pipeline_objects)
@@ -368,15 +459,6 @@ void ObjectCache::DestroyPipelineCache()
m_pipeline_cache = VK_NULL_HANDLE;
}
-void ObjectCache::ClearPipelineCache()
-{
- // Reallocate the pipeline cache object, so it starts fresh and we don't
- // save old pipelines to disk. This is for major changes, e.g. MSAA mode change.
- DestroyPipelineCache();
- if (!CreatePipelineCache(false))
- PanicAlert("Failed to re-create pipeline cache");
-}
-
void ObjectCache::SavePipelineCache()
{
size_t data_size;
diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.h b/Source/Core/VideoBackends/Vulkan/ObjectCache.h
index b991b8a3b8..26593d139d 100644
--- a/Source/Core/VideoBackends/Vulkan/ObjectCache.h
+++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.h
@@ -111,12 +111,17 @@ public:
// Perform at startup, create descriptor layouts, compiles all static shaders.
bool Initialize();
- // Find a pipeline by the specified description, if not found, attempts to create it
+ // Creates a pipeline for the specified description. The resulting pipeline, if successful
+ // is not stored anywhere, this is left up to the caller.
+ VkPipeline CreatePipeline(const PipelineInfo& info);
+
+ // Find a pipeline by the specified description, if not found, attempts to create it.
VkPipeline GetPipeline(const PipelineInfo& info);
- // Wipes out the pipeline cache, use when MSAA modes change, for example
- // Also destroys the data that would be stored in the disk cache.
- void ClearPipelineCache();
+ // Find a pipeline by the specified description, if not found, attempts to create it. If this
+ // resulted in a pipeline being created, the second field of the return value will be false,
+ // otherwise for a cache hit it will be true.
+ std::pair<VkPipeline, bool> GetPipelineWithCacheResult(const PipelineInfo& info);
// Saves the pipeline cache to disk. Call when shutting down.
void SavePipelineCache();
@@ -133,8 +138,12 @@ public:
VkShaderModule GetPassthroughVertexShader() const { return m_passthrough_vertex_shader; }
VkShaderModule GetScreenQuadGeometryShader() const { return m_screen_quad_geometry_shader; }
VkShaderModule GetPassthroughGeometryShader() const { return m_passthrough_geometry_shader; }
+ // Gets the filename of the specified type of cache object (e.g. vertex shader, pipeline).
+ std::string GetDiskCacheFileName(const char* type);
+
private:
bool CreatePipelineCache(bool load_from_disk);
+ bool ValidatePipelineCache(const u8* data, size_t data_length);
void DestroyPipelineCache();
void LoadShaderCaches();
void DestroyShaderCaches();
@@ -148,8 +157,6 @@ private:
void DestroySharedShaders();
void DestroySamplers();
- std::string GetDiskCacheFileName(const char* type);
-
std::array<VkDescriptorSetLayout, NUM_DESCRIPTOR_SETS> m_descriptor_set_layouts = {};
VkPipelineLayout m_standard_pipeline_layout = VK_NULL_HANDLE;
diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp
index 5348d6beec..1a82184fc4 100644
--- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp
+++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp
@@ -116,6 +116,9 @@ bool Renderer::Initialize()
m_bounding_box->GetGPUBufferSize());
}
+ // Ensure all pipelines previously used by the game have been created.
+ StateTracker::GetInstance()->LoadPipelineUIDCache();
+
// Various initialization routines will have executed commands on the command buffer.
// Execute what we have done before beginning the first frame.
g_command_buffer_mgr->PrepareToSubmitCommandBuffer();
@@ -1134,8 +1137,8 @@ void Renderer::CheckForConfigChanges()
g_command_buffer_mgr->WaitForGPUIdle();
RecompileShaders();
FramebufferManager::GetInstance()->RecompileShaders();
- g_object_cache->ClearPipelineCache();
g_object_cache->RecompileSharedShaders();
+ StateTracker::GetInstance()->LoadPipelineUIDCache();
}
// For vsync, we need to change the present mode, which means recreating the swap chain.
diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp
index bfd198886b..c7c4c77c54 100644
--- a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp
+++ b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp
@@ -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()
diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.h b/Source/Core/VideoBackends/Vulkan/StateTracker.h
index b31adec6b5..a42a9dc72d 100644
--- a/Source/Core/VideoBackends/Vulkan/StateTracker.h
+++ b/Source/Core/VideoBackends/Vulkan/StateTracker.h
@@ -9,6 +9,7 @@
#include <memory>
#include "Common/CommonTypes.h"
+#include "Common/LinearDiskCache.h"
#include "VideoBackends/Vulkan/Constants.h"
#include "VideoBackends/Vulkan/ObjectCache.h"
#include "VideoCommon/GeometryShaderGen.h"
@@ -111,15 +112,22 @@ public:
bool IsWithinRenderArea(s32 x, s32 y, u32 width, u32 height) const;
-private:
- bool Initialize();
+ // Reloads the UID cache, ensuring all pipelines used by the game so far have been created.
+ void LoadPipelineUIDCache();
- // 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();
+private:
+ // Serialized version of PipelineInfo, used when loading/saving the pipeline UID cache.
+ struct SerializedPipelineUID
+ {
+ u64 blend_state_bits;
+ u32 rasterizer_state_bits;
+ u32 depth_stencil_state_bits;
+ PortableVertexDeclaration vertex_decl;
+ VertexShaderUid vs_uid;
+ GeometryShaderUid gs_uid;
+ PixelShaderUid ps_uid;
+ VkPrimitiveTopology primitive_topology;
+ };
enum DITRY_FLAG : u32
{
@@ -140,6 +148,32 @@ private:
DIRTY_FLAG_ALL_DESCRIPTOR_SETS =
DIRTY_FLAG_VS_UBO | DIRTY_FLAG_GS_UBO | DIRTY_FLAG_PS_SAMPLERS | DIRTY_FLAG_PS_SSBO
};
+
+ bool Initialize();
+
+ // Appends the specified pipeline info, combined with the UIDs stored in the class.
+ // The info is here so that we can store variations of a UID, e.g. blend state.
+ void AppendToPipelineUIDCache(const PipelineInfo& info);
+
+ // Precaches a pipeline based on the UID information.
+ bool PrecachePipelineUID(const SerializedPipelineUID& uid);
+
+ // 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;
+
+ // Gets a pipeline state that can be used to draw the alpha pass with constant alpha enabled.
+ PipelineInfo GetAlphaPassPipelineConfig(const PipelineInfo& info) const;
+
+ // Obtains a Vulkan pipeline object for the specified pipeline configuration.
+ // Also adds this pipeline configuration to the UID cache if it is not present already.
+ VkPipeline GetPipelineAndCacheUID(const PipelineInfo& info);
+
+ bool UpdatePipeline();
+ bool UpdateDescriptorSet();
+ void UploadAllConstants();
+
+ // Which bindings/state has to be updated before the next draw.
u32 m_dirty_flags = 0;
// input assembly
@@ -194,5 +228,11 @@ private:
std::vector<u32> m_cpu_accesses_this_frame;
std::vector<u32> m_scheduled_command_buffer_kicks;
bool m_allow_background_execution = true;
+
+ // Draw state cache on disk
+ // We don't actually use the value field here, instead we generate the shaders from the uid
+ // on-demand. If all goes well, it should hit the shader and Vulkan pipeline cache, therefore
+ // loading should be reasonably efficient.
+ LinearDiskCache<SerializedPipelineUID, u32> m_uid_cache;
};
}
diff --git a/Source/Core/VideoBackends/Vulkan/VertexFormat.cpp b/Source/Core/VideoBackends/Vulkan/VertexFormat.cpp
index 27c1d06199..ba67472394 100644
--- a/Source/Core/VideoBackends/Vulkan/VertexFormat.cpp
+++ b/Source/Core/VideoBackends/Vulkan/VertexFormat.cpp
@@ -53,6 +53,19 @@ VertexFormat::VertexFormat(const PortableVertexDeclaration& in_vtx_decl)
SetupInputState();
}
+VertexFormat* VertexFormat::GetOrCreateMatchingFormat(const PortableVertexDeclaration& decl)
+{
+ auto vertex_format_map = VertexLoaderManager::GetNativeVertexFormatMap();
+ auto iter = vertex_format_map->find(decl);
+ if (iter == vertex_format_map->end())
+ {
+ auto ipair = vertex_format_map->emplace(decl, std::make_unique<VertexFormat>(decl));
+ iter = ipair.first;
+ }
+
+ return static_cast<VertexFormat*>(iter->second.get());
+}
+
void VertexFormat::MapAttributes()
{
m_num_attributes = 0;
diff --git a/Source/Core/VideoBackends/Vulkan/VertexFormat.h b/Source/Core/VideoBackends/Vulkan/VertexFormat.h
index 3614366e2f..ef2d31d748 100644
--- a/Source/Core/VideoBackends/Vulkan/VertexFormat.h
+++ b/Source/Core/VideoBackends/Vulkan/VertexFormat.h
@@ -16,6 +16,11 @@ class VertexFormat : public ::NativeVertexFormat
public:
VertexFormat(const PortableVertexDeclaration& in_vtx_decl);
+ // Creates or obtains a pointer to a VertexFormat representing decl.
+ // If this results in a VertexFormat being created, if the game later uses a matching vertex
+ // declaration, the one that was previously created will be used.
+ static VertexFormat* GetOrCreateMatchingFormat(const PortableVertexDeclaration& decl);
+
// Passed to pipeline state creation
const VkPipelineVertexInputStateCreateInfo& GetVertexInputStateInfo() const
{