From e5157a5360280fd195fe9489b2f4578e7ca6bb55 Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Mon, 9 Jan 2023 21:26:15 +0100 Subject: [PATCH] [dxvk] Add pre-rasterization pipeline libraries to the state cache This allows compiling tessellation or geometry shader pipelines early while still using the pipeline library path. Also removes compute shaders. Since API-provided compute shaders are always compiled early, supporting them is no longer needed. --- src/dxvk/dxvk_compute.cpp | 15 +-- src/dxvk/dxvk_compute.h | 3 - src/dxvk/dxvk_pipemanager.cpp | 36 +++--- src/dxvk/dxvk_pipemanager.h | 13 ++- src/dxvk/dxvk_state_cache.cpp | 187 +++++++++++++++++++----------- src/dxvk/dxvk_state_cache.h | 26 ++--- src/dxvk/dxvk_state_cache_types.h | 14 ++- 7 files changed, 175 insertions(+), 119 deletions(-) diff --git a/src/dxvk/dxvk_compute.cpp b/src/dxvk/dxvk_compute.cpp index fdc7394d..93ebfa5e 100644 --- a/src/dxvk/dxvk_compute.cpp +++ b/src/dxvk/dxvk_compute.cpp @@ -60,10 +60,8 @@ namespace dxvk { std::lock_guard lock(m_mutex); instance = this->findInstance(state); - if (!instance) { + if (!instance) instance = this->createInstance(state); - this->writePipelineStateToCache(state); - } } return instance->handle; @@ -139,17 +137,6 @@ namespace dxvk { } - void DxvkComputePipeline::writePipelineStateToCache( - const DxvkComputePipelineStateInfo& state) const { - DxvkStateCacheKey key; - - if (m_shaders.cs != nullptr) - key.cs = m_shaders.cs->getShaderKey(); - - m_stateCache->addComputePipeline(key, state); - } - - void DxvkComputePipeline::logPipelineState( LogLevel level, const DxvkComputePipelineStateInfo& state) const { diff --git a/src/dxvk/dxvk_compute.h b/src/dxvk/dxvk_compute.h index 141a1133..cd758ae0 100644 --- a/src/dxvk/dxvk_compute.h +++ b/src/dxvk/dxvk_compute.h @@ -149,9 +149,6 @@ namespace dxvk { void destroyPipeline( VkPipeline pipeline); - void writePipelineStateToCache( - const DxvkComputePipelineStateInfo& state) const; - void logPipelineState( LogLevel level, const DxvkComputePipelineStateInfo& state) const; diff --git a/src/dxvk/dxvk_pipemanager.cpp b/src/dxvk/dxvk_pipemanager.cpp index b3754ab5..07e75fd5 100644 --- a/src/dxvk/dxvk_pipemanager.cpp +++ b/src/dxvk/dxvk_pipemanager.cpp @@ -297,12 +297,9 @@ namespace dxvk { DxvkShaderPipelineLibraryKey vsKey; vsKey.addShader(shaders.vs); - if (shaders.tcs != nullptr) - vsKey.addShader(shaders.tcs); - if (shaders.tes != nullptr) - vsKey.addShader(shaders.tes); - if (shaders.gs != nullptr) - vsKey.addShader(shaders.gs); + if (shaders.tcs != nullptr) vsKey.addShader(shaders.tcs); + if (shaders.tes != nullptr) vsKey.addShader(shaders.tes); + if (shaders.gs != nullptr) vsKey.addShader(shaders.gs); if (vsKey.canUsePipelineLibrary()) { vsLibrary = findPipelineLibraryLocked(vsKey); @@ -313,6 +310,17 @@ namespace dxvk { // Don't dispatch the pipeline library to a worker thread // since it should be compiled on demand anyway. vsLibrary = createPipelineLibraryLocked(vsKey); + + // Register the pipeline library with the state cache + // so that subsequent runs can still compile it early + DxvkStateCacheKey shaderKeys; + shaderKeys.vs = shaders.vs->getShaderKey(); + + if (shaders.tcs != nullptr) shaderKeys.tcs = shaders.tcs->getShaderKey(); + if (shaders.tes != nullptr) shaderKeys.tes = shaders.tes->getShaderKey(); + if (shaders.gs != nullptr) shaderKeys.gs = shaders.gs->getShaderKey(); + + m_stateCache.addPipelineLibrary(shaderKeys); } } @@ -335,6 +343,13 @@ namespace dxvk { } + DxvkShaderPipelineLibrary* DxvkPipelineManager::createShaderPipelineLibrary( + const DxvkShaderPipelineLibraryKey& key) { + std::lock_guard lock(m_mutex); + return createPipelineLibraryLocked(key); + } + + DxvkGraphicsPipelineVertexInputLibrary* DxvkPipelineManager::createVertexInputLibrary( const DxvkGraphicsPipelineVertexInputState& state) { std::lock_guard lock(m_mutex); @@ -373,7 +388,7 @@ namespace dxvk { DxvkShaderPipelineLibraryKey key; key.addShader(shader); - auto library = createPipelineLibrary(key); + auto library = createShaderPipelineLibrary(key); m_workers.compilePipelineLibrary(library, DxvkPipelinePriority::Normal); } @@ -452,13 +467,6 @@ namespace dxvk { } - DxvkShaderPipelineLibrary* DxvkPipelineManager::createPipelineLibrary( - const DxvkShaderPipelineLibraryKey& key) { - std::lock_guard lock(m_mutex); - return createPipelineLibraryLocked(key); - } - - DxvkShaderPipelineLibrary* DxvkPipelineManager::createPipelineLibraryLocked( const DxvkShaderPipelineLibraryKey& key) { auto bindings = key.getBindings(); diff --git a/src/dxvk/dxvk_pipemanager.h b/src/dxvk/dxvk_pipemanager.h index 4a1f7922..3ae32de1 100644 --- a/src/dxvk/dxvk_pipemanager.h +++ b/src/dxvk/dxvk_pipemanager.h @@ -185,6 +185,16 @@ namespace dxvk { DxvkGraphicsPipeline* createGraphicsPipeline( const DxvkGraphicsPipelineShaders& shaders); + /** + * \brief Creates a pipeline library with a given set of shaders + * + * If a pipeline library already exists, it will be returned. + * Otherwise, a new pipeline library will be created. + * \param [in] key Shader set + */ + DxvkShaderPipelineLibrary* createShaderPipelineLibrary( + const DxvkShaderPipelineLibraryKey& key); + /** * \brief Retrieves a vertex input pipeline library * @@ -294,9 +304,6 @@ namespace dxvk { DxvkBindingLayoutObjects* createPipelineLayout( const DxvkBindingLayout& layout); - DxvkShaderPipelineLibrary* createPipelineLibrary( - const DxvkShaderPipelineLibraryKey& key); - DxvkShaderPipelineLibrary* createPipelineLibraryLocked( const DxvkShaderPipelineLibraryKey& key); diff --git a/src/dxvk/dxvk_state_cache.cpp b/src/dxvk/dxvk_state_cache.cpp index b1d71b6c..b0c1f131 100644 --- a/src/dxvk/dxvk_state_cache.cpp +++ b/src/dxvk/dxvk_state_cache.cpp @@ -12,6 +12,16 @@ namespace dxvk { * \brief Packed entry header */ struct DxvkStateCacheEntryHeader { + uint32_t entryType : 1; + uint32_t stageMask : 5; + uint32_t entrySize : 26; + }; + + + /** + * \brief Version 8 entry header + */ + struct DxvkStateCacheEntryHeaderV8 { uint32_t stageMask : 8; uint32_t entrySize : 24; }; @@ -44,6 +54,28 @@ namespace dxvk { return read(data); } + bool read(DxvkStateCacheKey& shaders, uint32_t version, VkShaderStageFlags stageFlags) { + DxvkShaderKey dummyKey; + + std::array, 6> stages = {{ + { VK_SHADER_STAGE_VERTEX_BIT, &shaders.vs }, + { VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, &shaders.tcs }, + { VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, &shaders.tes }, + { VK_SHADER_STAGE_GEOMETRY_BIT, &shaders.gs }, + { VK_SHADER_STAGE_FRAGMENT_BIT, &shaders.fs }, + { VK_SHADER_STAGE_COMPUTE_BIT, &dummyKey }, + }}; + + for (uint32_t i = 0; i < stages.size(); i++) { + if (stageFlags & stages[i].first) { + if (!read(*stages[i].second, version)) + return false; + } + } + + return true; + } + bool read(DxvkBindingMaskV10& data, uint32_t version) { // v11 removes this field if (version >= 11) @@ -208,8 +240,7 @@ namespace dxvk { && this->tcs.eq(key.tcs) && this->tes.eq(key.tes) && this->gs.eq(key.gs) - && this->fs.eq(key.fs) - && this->cs.eq(key.cs); + && this->fs.eq(key.fs); } @@ -220,7 +251,6 @@ namespace dxvk { hash.add(this->tes.hash()); hash.add(this->gs.hash()); hash.add(this->fs.hash()); - hash.add(this->cs.hash()); return hash; } @@ -257,52 +287,52 @@ namespace dxvk { } - void DxvkStateCache::addGraphicsPipeline( - const DxvkStateCacheKey& shaders, - const DxvkGraphicsPipelineStateInfo& state) { + void DxvkStateCache::addPipelineLibrary( + const DxvkStateCacheKey& shaders) { if (!m_enable || shaders.vs.eq(g_nullShaderKey)) return; - + // Do not add an entry that is already in the cache auto entries = m_entryMap.equal_range(shaders); for (auto e = entries.first; e != entries.second; e++) { - const DxvkStateCacheEntry& entry = m_entries[e->second]; - - if (entry.gpState == state) + if (m_entries[e->second].type == DxvkStateCacheEntryType::PipelineLibrary) return; } // Queue a job to write this pipeline to the cache std::unique_lock lock(m_writerLock); - m_writerQueue.push({ shaders, state, - DxvkComputePipelineStateInfo(), g_nullHash }); + m_writerQueue.push({ + DxvkStateCacheEntryType::PipelineLibrary, shaders, + DxvkGraphicsPipelineStateInfo(), g_nullHash }); m_writerCond.notify_one(); createWriter(); } - void DxvkStateCache::addComputePipeline( + void DxvkStateCache::addGraphicsPipeline( const DxvkStateCacheKey& shaders, - const DxvkComputePipelineStateInfo& state) { - if (!m_enable || shaders.cs.eq(g_nullShaderKey)) + const DxvkGraphicsPipelineStateInfo& state) { + if (!m_enable || shaders.vs.eq(g_nullShaderKey)) return; // Do not add an entry that is already in the cache auto entries = m_entryMap.equal_range(shaders); for (auto e = entries.first; e != entries.second; e++) { - if (m_entries[e->second].cpState == state) + if (m_entries[e->second].type == DxvkStateCacheEntryType::MonolithicPipeline + && m_entries[e->second].gpState == state) return; } // Queue a job to write this pipeline to the cache std::unique_lock lock(m_writerLock); - m_writerQueue.push({ shaders, - DxvkGraphicsPipelineStateInfo(), state, g_nullHash }); + m_writerQueue.push({ + DxvkStateCacheEntryType::MonolithicPipeline, + shaders, state, g_nullHash }); m_writerCond.notify_one(); createWriter(); @@ -334,8 +364,7 @@ namespace dxvk { || !getShaderByKey(p->second.tcs, item.gp.tcs) || !getShaderByKey(p->second.tes, item.gp.tes) || !getShaderByKey(p->second.gs, item.gp.gs) - || !getShaderByKey(p->second.fs, item.gp.fs) - || !getShaderByKey(p->second.cs, item.cp.cs)) + || !getShaderByKey(p->second.fs, item.gp.fs)) continue; if (!workerLock) @@ -412,23 +441,35 @@ namespace dxvk { key.tes = getShaderKey(item.gp.tes); key.gs = getShaderKey(item.gp.gs); key.fs = getShaderKey(item.gp.fs); - key.cs = getShaderKey(item.cp.cs); - if (item.cp.cs == nullptr) { - auto pipeline = m_pipeManager->createGraphicsPipeline(item.gp); - auto entries = m_entryMap.equal_range(key); + DxvkGraphicsPipeline* pipeline = nullptr; + auto entries = m_entryMap.equal_range(key); - for (auto e = entries.first; e != entries.second; e++) { - const auto& entry = m_entries[e->second]; - m_pipeWorkers->compileGraphicsPipeline(pipeline, entry.gpState); - } - } else { - auto pipeline = m_pipeManager->createComputePipeline(item.cp); - auto entries = m_entryMap.equal_range(key); + for (auto e = entries.first; e != entries.second; e++) { + const auto& entry = m_entries[e->second]; - for (auto e = entries.first; e != entries.second; e++) { - const auto& entry = m_entries[e->second]; - m_pipeWorkers->compileComputePipeline(pipeline, entry.cpState); + switch (entry.type) { + case DxvkStateCacheEntryType::MonolithicPipeline: { + if (!pipeline) + pipeline = m_pipeManager->createGraphicsPipeline(item.gp); + + m_pipeWorkers->compileGraphicsPipeline(pipeline, entry.gpState); + } break; + + case DxvkStateCacheEntryType::PipelineLibrary: { + if (!m_device->canUseGraphicsPipelineLibrary() || item.gp.vs == nullptr) + break; + + DxvkShaderPipelineLibraryKey libraryKey; + libraryKey.addShader(item.gp.vs); + + if (item.gp.tcs != nullptr) libraryKey.addShader(item.gp.tcs); + if (item.gp.tes != nullptr) libraryKey.addShader(item.gp.tes); + if (item.gp.gs != nullptr) libraryKey.addShader(item.gp.gs); + + auto pipelineLibrary = m_pipeManager->createShaderPipelineLibrary(libraryKey); + m_pipeWorkers->compilePipelineLibrary(pipelineLibrary, DxvkPipelinePriority::Normal); + } break; } } } @@ -483,7 +524,6 @@ namespace dxvk { mapShaderToPipeline(entry.shaders.tes, entry.shaders); mapShaderToPipeline(entry.shaders.gs, entry.shaders); mapShaderToPipeline(entry.shaders.fs, entry.shaders); - mapShaderToPipeline(entry.shaders.cs, entry.shaders); } else if (ifile) { numInvalidEntries += 1; } @@ -531,11 +571,28 @@ namespace dxvk { DxvkStateCacheEntry& entry) const { // Read entry metadata and actual data DxvkStateCacheEntryHeader header; + DxvkStateCacheEntryHeaderV8 headerV8; DxvkStateCacheEntryData data; + VkShaderStageFlags stageMask; Sha1Hash hash; - - if (!stream.read(reinterpret_cast(&header), sizeof(header)) - || !stream.read(reinterpret_cast(&hash), sizeof(hash)) + + if (version >= 16) { + if (!stream.read(reinterpret_cast(&header), sizeof(header))) + return false; + + stageMask = VkShaderStageFlags(header.stageMask); + } else { + if (!stream.read(reinterpret_cast(&headerV8), sizeof(headerV8))) + return false; + + header.entryType = uint32_t(DxvkStateCacheEntryType::MonolithicPipeline); + header.stageMask = headerV8.stageMask & VK_SHADER_STAGE_ALL_GRAPHICS; + header.entrySize = headerV8.entrySize; + + stageMask = VkShaderStageFlags(headerV8.stageMask); + } + + if (!stream.read(reinterpret_cast(&hash), sizeof(hash)) || !data.readFromStream(stream, header.entrySize)) return false; @@ -543,16 +600,15 @@ namespace dxvk { if (hash != data.computeHash()) return false; - // Read shader hashes - VkShaderStageFlags stageMask = VkShaderStageFlags(header.stageMask); - auto keys = &entry.shaders.vs; + // Set up entry metadata + entry.type = DxvkStateCacheEntryType(header.entryType); - for (uint32_t i = 0; i < 6; i++) { - if (stageMask & VkShaderStageFlagBits(1 << i)) - data.read(keys[i], version); - else - keys[i] = g_nullShaderKey; - } + // Read shader hashes + auto entryType = DxvkStateCacheEntryType(header.entryType); + data.read(entry.shaders, version, stageMask); + + if (entryType == DxvkStateCacheEntryType::PipelineLibrary) + return true; DxvkBindingMaskV10 dummyBindingMask = { }; @@ -610,10 +666,6 @@ namespace dxvk { } // Read non-zero spec constants - auto& sc = (stageMask & VK_SHADER_STAGE_COMPUTE_BIT) - ? entry.cpState.sc - : entry.gpState.sc; - uint32_t specConstantMask = 0; if (!data.read(specConstantMask, version)) @@ -621,11 +673,15 @@ namespace dxvk { for (uint32_t i = 0; i < MaxNumSpecConstants; i++) { if (specConstantMask & (1 << i)) { - if (!data.read(sc.specConstants[i], version)) + if (!data.read(entry.gpState.sc.specConstants[i], version)) return false; } } + // Compute shaders are no longer supported + if (stageMask & VK_SHADER_STAGE_COMPUTE_BIT) + return false; + return true; } @@ -646,7 +702,7 @@ namespace dxvk { } } - if (!(stageMask & VK_SHADER_STAGE_COMPUTE_BIT)) { + if (entry.type != DxvkStateCacheEntryType::PipelineLibrary) { // Write out common pipeline state data.write(entry.gpState.ia); data.write(entry.gpState.il); @@ -671,28 +727,25 @@ namespace dxvk { for (uint32_t i = 0; i < entry.gpState.il.bindingCount(); i++) data.write(entry.gpState.ilBindings[i]); - } - // Write out all non-zero spec constants - auto& sc = (stageMask & VK_SHADER_STAGE_COMPUTE_BIT) - ? entry.cpState.sc - : entry.gpState.sc; + // Write out all non-zero spec constants + uint32_t specConstantMask = 0; - uint32_t specConstantMask = 0; + for (uint32_t i = 0; i < MaxNumSpecConstants; i++) + specConstantMask |= entry.gpState.sc.specConstants[i] ? (1 << i) : 0; - for (uint32_t i = 0; i < MaxNumSpecConstants; i++) - specConstantMask |= sc.specConstants[i] ? (1 << i) : 0; + data.write(specConstantMask); - data.write(specConstantMask); - - for (uint32_t i = 0; i < MaxNumSpecConstants; i++) { - if (specConstantMask & (1 << i)) - data.write(sc.specConstants[i]); + for (uint32_t i = 0; i < MaxNumSpecConstants; i++) { + if (specConstantMask & (1 << i)) + data.write(entry.gpState.sc.specConstants[i]); + } } // General layout: header -> hash -> data DxvkStateCacheEntryHeader header; - header.stageMask = uint8_t(stageMask); + header.entryType = uint32_t(entry.type); + header.stageMask = uint32_t(stageMask); header.entrySize = data.size(); Sha1Hash hash = data.computeHash(); diff --git a/src/dxvk/dxvk_state_cache.h b/src/dxvk/dxvk_state_cache.h index 24ec1001..0e72138a 100644 --- a/src/dxvk/dxvk_state_cache.h +++ b/src/dxvk/dxvk_state_cache.h @@ -37,30 +37,27 @@ namespace dxvk { ~DxvkStateCache(); /** - * Adds a graphics pipeline to the cache + * \brief Adds pipeline library to the cache + * + * If the pipeline is not already cached, this + * will write a new pipeline to the cache file. + * \param [in] shaders Shader keys + */ + void addPipelineLibrary( + const DxvkStateCacheKey& shaders); + + /** + * \brief Adds a graphics pipeline to the cache * * If the pipeline is not already cached, this * will write a new pipeline to the cache file. * \param [in] shaders Shader keys * \param [in] state Graphics pipeline state - * \param [in] format Render pass format */ void addGraphicsPipeline( const DxvkStateCacheKey& shaders, const DxvkGraphicsPipelineStateInfo& state); - /** - * Adds a compute pipeline to the cache - * - * If the pipeline is not already cached, this - * will write a new pipeline to the cache file. - * \param [in] shaders Shader keys - * \param [in] state Compute pipeline state - */ - void addComputePipeline( - const DxvkStateCacheKey& shaders, - const DxvkComputePipelineStateInfo& state); - /** * \brief Registers a newly compiled shader * @@ -83,7 +80,6 @@ namespace dxvk { struct WorkerItem { DxvkGraphicsPipelineShaders gp; - DxvkComputePipelineShaders cp; }; DxvkDevice* m_device; diff --git a/src/dxvk/dxvk_state_cache_types.h b/src/dxvk/dxvk_state_cache_types.h index c2f81868..86d99a55 100644 --- a/src/dxvk/dxvk_state_cache_types.h +++ b/src/dxvk/dxvk_state_cache_types.h @@ -19,13 +19,21 @@ namespace dxvk { DxvkShaderKey tes; DxvkShaderKey gs; DxvkShaderKey fs; - DxvkShaderKey cs; bool eq(const DxvkStateCacheKey& key) const; size_t hash() const; }; + + /** + * \brief State entry type + */ + enum class DxvkStateCacheEntryType : uint32_t { + MonolithicPipeline = 0, + PipelineLibrary = 1, + }; + /** * \brief State entry @@ -36,9 +44,9 @@ namespace dxvk { * that is used as a check sum to verify integrity. */ struct DxvkStateCacheEntry { + DxvkStateCacheEntryType type; DxvkStateCacheKey shaders; DxvkGraphicsPipelineStateInfo gpState; - DxvkComputePipelineStateInfo cpState; Sha1Hash hash; }; @@ -52,7 +60,7 @@ namespace dxvk { */ struct DxvkStateCacheHeader { char magic[4] = { 'D', 'X', 'V', 'K' }; - uint32_t version = 15; + uint32_t version = 16; uint32_t entrySize = 0; /* no longer meaningful */ };