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;
|
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
|
// Declare descriptors for empty vertex buffers/attributes
|
||||||
static const VkPipelineVertexInputStateCreateInfo empty_vertex_input_state = {
|
static const VkPipelineVertexInputStateCreateInfo empty_vertex_input_state = {
|
||||||
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // VkStructureType sType
|
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
|
-1 // int32_t basePipelineIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
VkPipeline pipeline;
|
||||||
VkResult res = vkCreateGraphicsPipelines(g_vulkan_context->GetDevice(), m_pipeline_cache, 1,
|
VkResult res = vkCreateGraphicsPipelines(g_vulkan_context->GetDevice(), m_pipeline_cache, 1,
|
||||||
&pipeline_info, nullptr, &pipeline);
|
&pipeline_info, nullptr, &pipeline);
|
||||||
if (res != VK_SUCCESS)
|
if (res != VK_SUCCESS)
|
||||||
|
{
|
||||||
LOG_VULKAN_ERROR(res, "vkCreateGraphicsPipelines failed: ");
|
LOG_VULKAN_ERROR(res, "vkCreateGraphicsPipelines failed: ");
|
||||||
|
return VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
m_pipeline_objects.emplace(info, pipeline);
|
|
||||||
return 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)
|
std::string ObjectCache::GetDiskCacheFileName(const char* type)
|
||||||
{
|
{
|
||||||
return StringFromFormat("%svulkan-%s-%s.cache", File::GetUserPath(D_SHADERCACHE_IDX).c_str(),
|
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();
|
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 = {
|
VkPipelineCacheCreateInfo info = {
|
||||||
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
|
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
|
||||||
nullptr, // const void* pNext
|
nullptr, // const void* pNext
|
||||||
|
@ -355,6 +376,76 @@ bool ObjectCache::CreatePipelineCache(bool load_from_disk)
|
||||||
return false;
|
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()
|
void ObjectCache::DestroyPipelineCache()
|
||||||
{
|
{
|
||||||
for (const auto& it : m_pipeline_objects)
|
for (const auto& it : m_pipeline_objects)
|
||||||
|
@ -368,15 +459,6 @@ void ObjectCache::DestroyPipelineCache()
|
||||||
m_pipeline_cache = VK_NULL_HANDLE;
|
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()
|
void ObjectCache::SavePipelineCache()
|
||||||
{
|
{
|
||||||
size_t data_size;
|
size_t data_size;
|
||||||
|
|
|
@ -111,12 +111,17 @@ public:
|
||||||
// Perform at startup, create descriptor layouts, compiles all static shaders.
|
// Perform at startup, create descriptor layouts, compiles all static shaders.
|
||||||
bool Initialize();
|
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);
|
VkPipeline GetPipeline(const PipelineInfo& info);
|
||||||
|
|
||||||
// Wipes out the pipeline cache, use when MSAA modes change, for example
|
// Find a pipeline by the specified description, if not found, attempts to create it. If this
|
||||||
// Also destroys the data that would be stored in the disk cache.
|
// resulted in a pipeline being created, the second field of the return value will be false,
|
||||||
void ClearPipelineCache();
|
// 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.
|
// Saves the pipeline cache to disk. Call when shutting down.
|
||||||
void SavePipelineCache();
|
void SavePipelineCache();
|
||||||
|
@ -133,8 +138,12 @@ public:
|
||||||
VkShaderModule GetPassthroughVertexShader() const { return m_passthrough_vertex_shader; }
|
VkShaderModule GetPassthroughVertexShader() const { return m_passthrough_vertex_shader; }
|
||||||
VkShaderModule GetScreenQuadGeometryShader() const { return m_screen_quad_geometry_shader; }
|
VkShaderModule GetScreenQuadGeometryShader() const { return m_screen_quad_geometry_shader; }
|
||||||
VkShaderModule GetPassthroughGeometryShader() const { return m_passthrough_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:
|
private:
|
||||||
bool CreatePipelineCache(bool load_from_disk);
|
bool CreatePipelineCache(bool load_from_disk);
|
||||||
|
bool ValidatePipelineCache(const u8* data, size_t data_length);
|
||||||
void DestroyPipelineCache();
|
void DestroyPipelineCache();
|
||||||
void LoadShaderCaches();
|
void LoadShaderCaches();
|
||||||
void DestroyShaderCaches();
|
void DestroyShaderCaches();
|
||||||
|
@ -148,8 +157,6 @@ private:
|
||||||
void DestroySharedShaders();
|
void DestroySharedShaders();
|
||||||
void DestroySamplers();
|
void DestroySamplers();
|
||||||
|
|
||||||
std::string GetDiskCacheFileName(const char* type);
|
|
||||||
|
|
||||||
std::array<VkDescriptorSetLayout, NUM_DESCRIPTOR_SETS> m_descriptor_set_layouts = {};
|
std::array<VkDescriptorSetLayout, NUM_DESCRIPTOR_SETS> m_descriptor_set_layouts = {};
|
||||||
|
|
||||||
VkPipelineLayout m_standard_pipeline_layout = VK_NULL_HANDLE;
|
VkPipelineLayout m_standard_pipeline_layout = VK_NULL_HANDLE;
|
||||||
|
|
|
@ -116,6 +116,9 @@ bool Renderer::Initialize()
|
||||||
m_bounding_box->GetGPUBufferSize());
|
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.
|
// Various initialization routines will have executed commands on the command buffer.
|
||||||
// Execute what we have done before beginning the first frame.
|
// Execute what we have done before beginning the first frame.
|
||||||
g_command_buffer_mgr->PrepareToSubmitCommandBuffer();
|
g_command_buffer_mgr->PrepareToSubmitCommandBuffer();
|
||||||
|
@ -1134,8 +1137,8 @@ void Renderer::CheckForConfigChanges()
|
||||||
g_command_buffer_mgr->WaitForGPUIdle();
|
g_command_buffer_mgr->WaitForGPUIdle();
|
||||||
RecompileShaders();
|
RecompileShaders();
|
||||||
FramebufferManager::GetInstance()->RecompileShaders();
|
FramebufferManager::GetInstance()->RecompileShaders();
|
||||||
g_object_cache->ClearPipelineCache();
|
|
||||||
g_object_cache->RecompileSharedShaders();
|
g_object_cache->RecompileSharedShaders();
|
||||||
|
StateTracker::GetInstance()->LoadPipelineUIDCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
// For vsync, we need to change the present mode, which means recreating the swap chain.
|
// 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/ObjectCache.h"
|
||||||
#include "VideoBackends/Vulkan/StreamBuffer.h"
|
#include "VideoBackends/Vulkan/StreamBuffer.h"
|
||||||
#include "VideoBackends/Vulkan/Util.h"
|
#include "VideoBackends/Vulkan/Util.h"
|
||||||
|
#include "VideoBackends/Vulkan/VertexFormat.h"
|
||||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||||
|
|
||||||
#include "VideoCommon/GeometryShaderManager.h"
|
#include "VideoCommon/GeometryShaderManager.h"
|
||||||
|
@ -116,6 +117,93 @@ bool StateTracker::Initialize()
|
||||||
return true;
|
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)
|
void StateTracker::SetVertexBuffer(VkBuffer buffer, VkDeviceSize offset)
|
||||||
{
|
{
|
||||||
if (m_vertex_buffer == buffer && m_vertex_buffer_offset == offset)
|
if (m_vertex_buffer == buffer && m_vertex_buffer_offset == offset)
|
||||||
|
@ -793,41 +881,54 @@ void StateTracker::EndClearRenderPass()
|
||||||
EndRenderPass();
|
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()
|
bool StateTracker::UpdatePipeline()
|
||||||
{
|
{
|
||||||
// We need at least a vertex and fragment shader
|
// We need at least a vertex and fragment shader
|
||||||
if (m_pipeline_state.vs == VK_NULL_HANDLE || m_pipeline_state.ps == VK_NULL_HANDLE)
|
if (m_pipeline_state.vs == VK_NULL_HANDLE || m_pipeline_state.ps == VK_NULL_HANDLE)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Grab a new pipeline object, this can fail
|
// Grab a new pipeline object, this can fail.
|
||||||
if (m_dstalpha_mode != DSTALPHA_ALPHA_PASS)
|
// 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);
|
// We need to retain the existing state, since we don't want to break the next draw.
|
||||||
if (m_pipeline_object == VK_NULL_HANDLE)
|
PipelineInfo temp_info = GetAlphaPassPipelineConfig(m_pipeline_state);
|
||||||
return false;
|
m_pipeline_object = GetPipelineAndCacheUID(temp_info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We need to make a few modifications to the pipeline object, but retain
|
m_pipeline_object = GetPipelineAndCacheUID(m_pipeline_state);
|
||||||
// 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;
|
m_dirty_flags |= DIRTY_FLAG_PIPELINE_BINDING;
|
||||||
return true;
|
return m_pipeline_object != VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StateTracker::UpdateDescriptorSet()
|
bool StateTracker::UpdateDescriptorSet()
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Common/LinearDiskCache.h"
|
||||||
#include "VideoBackends/Vulkan/Constants.h"
|
#include "VideoBackends/Vulkan/Constants.h"
|
||||||
#include "VideoBackends/Vulkan/ObjectCache.h"
|
#include "VideoBackends/Vulkan/ObjectCache.h"
|
||||||
#include "VideoCommon/GeometryShaderGen.h"
|
#include "VideoCommon/GeometryShaderGen.h"
|
||||||
|
@ -111,15 +112,22 @@ public:
|
||||||
|
|
||||||
bool IsWithinRenderArea(s32 x, s32 y, u32 width, u32 height) const;
|
bool IsWithinRenderArea(s32 x, s32 y, u32 width, u32 height) const;
|
||||||
|
|
||||||
private:
|
// Reloads the UID cache, ensuring all pipelines used by the game so far have been created.
|
||||||
bool Initialize();
|
void LoadPipelineUIDCache();
|
||||||
|
|
||||||
// Check that the specified viewport is within the render area.
|
private:
|
||||||
// If not, ends the render pass if it is a clear render pass.
|
// Serialized version of PipelineInfo, used when loading/saving the pipeline UID cache.
|
||||||
bool IsViewportWithinRenderArea() const;
|
struct SerializedPipelineUID
|
||||||
bool UpdatePipeline();
|
{
|
||||||
bool UpdateDescriptorSet();
|
u64 blend_state_bits;
|
||||||
void UploadAllConstants();
|
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
|
enum DITRY_FLAG : u32
|
||||||
{
|
{
|
||||||
|
@ -140,6 +148,32 @@ private:
|
||||||
DIRTY_FLAG_ALL_DESCRIPTOR_SETS =
|
DIRTY_FLAG_ALL_DESCRIPTOR_SETS =
|
||||||
DIRTY_FLAG_VS_UBO | DIRTY_FLAG_GS_UBO | DIRTY_FLAG_PS_SAMPLERS | DIRTY_FLAG_PS_SSBO
|
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;
|
u32 m_dirty_flags = 0;
|
||||||
|
|
||||||
// input assembly
|
// input assembly
|
||||||
|
@ -194,5 +228,11 @@ private:
|
||||||
std::vector<u32> m_cpu_accesses_this_frame;
|
std::vector<u32> m_cpu_accesses_this_frame;
|
||||||
std::vector<u32> m_scheduled_command_buffer_kicks;
|
std::vector<u32> m_scheduled_command_buffer_kicks;
|
||||||
bool m_allow_background_execution = true;
|
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();
|
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()
|
void VertexFormat::MapAttributes()
|
||||||
{
|
{
|
||||||
m_num_attributes = 0;
|
m_num_attributes = 0;
|
||||||
|
|
|
@ -16,6 +16,11 @@ class VertexFormat : public ::NativeVertexFormat
|
||||||
public:
|
public:
|
||||||
VertexFormat(const PortableVertexDeclaration& in_vtx_decl);
|
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
|
// Passed to pipeline state creation
|
||||||
const VkPipelineVertexInputStateCreateInfo& GetVertexInputStateInfo() const
|
const VkPipelineVertexInputStateCreateInfo& GetVertexInputStateInfo() const
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue