Merge pull request #4449 from stenzek/vulkan-pipeline-cache
Vulkan: Implement pipeline UID cache
This commit is contained in:
commit
da87580dc1
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue