From b756c229a976c7a5f32b2ea1d7f5f90c0787bfcd Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Fri, 18 Oct 2019 00:33:50 +0200 Subject: [PATCH] [dxvk] Implement more compact state cache data format (v8) Omits a lot of unnecessary data and considerably reduces the state cache file size, often by over 80%. --- src/dxvk/dxvk_state_cache.cpp | 280 +++++++++++++++++++++++++++++- src/dxvk/dxvk_state_cache.h | 5 + src/dxvk/dxvk_state_cache_types.h | 4 +- 3 files changed, 282 insertions(+), 7 deletions(-) diff --git a/src/dxvk/dxvk_state_cache.cpp b/src/dxvk/dxvk_state_cache.cpp index 2dd423c0..9972f53a 100644 --- a/src/dxvk/dxvk_state_cache.cpp +++ b/src/dxvk/dxvk_state_cache.cpp @@ -7,6 +7,79 @@ namespace dxvk { static const Sha1Hash g_nullHash = Sha1Hash::compute(nullptr, 0); static const DxvkShaderKey g_nullShaderKey = DxvkShaderKey(); + + /** + * \brief Packed entry header + */ + struct DxvkStateCacheEntryHeader { + uint32_t stageMask : 8; + uint32_t entrySize : 24; + }; + + + /** + * \brief State cache entry data + * + * Stores data for a single cache entry and + * provides convenience methods to access it. + */ + class DxvkStateCacheEntryData { + constexpr static size_t MaxSize = 1024; + public: + + size_t size() const { + return m_size; + } + + const char* data() const { + return m_data; + } + + Sha1Hash computeHash() const { + return Sha1Hash::compute(m_data, m_size); + } + + template + bool read(T& data) { + if (m_read + sizeof(T) > m_size) + return false; + + std::memcpy(&data, &m_data[m_read], sizeof(T)); + m_read += sizeof(T); + return true; + } + + template + bool write(const T& data) { + if (m_size + sizeof(T) > MaxSize) + return false; + + std::memcpy(&m_data[m_size], &data, sizeof(T)); + m_size += sizeof(T); + return true; + } + + bool readFromStream(std::istream& stream, size_t size) { + if (size > MaxSize) + return false; + + if (!stream.read(m_data, size)) + return false; + + m_size = size; + m_read = 0; + return true; + } + + private: + + size_t m_size = 0; + size_t m_read = 0; + char m_data[MaxSize]; + + }; + + template bool readCacheEntryTyped(std::istream& stream, T& entry) { auto data = reinterpret_cast(&entry); @@ -304,6 +377,8 @@ namespace dxvk { expectedSize = sizeof(DxvkStateCacheEntryV5); else if (curHeader.version <= 6) expectedSize = sizeof(DxvkStateCacheEntryV6); + else if (curHeader.version <= 7) + expectedSize = sizeof(DxvkStateCacheEntry); if (curHeader.entrySize != expectedSize) { Logger::warn("DXVK: State cache entry size changed"); @@ -381,7 +456,7 @@ namespace dxvk { } - bool DxvkStateCache::readCacheEntry( + bool DxvkStateCache::readCacheEntryV7( uint32_t version, std::istream& stream, DxvkStateCacheEntry& entry) const { @@ -419,15 +494,210 @@ namespace dxvk { } + bool DxvkStateCache::readCacheEntry( + uint32_t version, + std::istream& stream, + DxvkStateCacheEntry& entry) const { + if (version < 8) + return readCacheEntryV7(version, stream, entry); + + // Read entry metadata and actual data + DxvkStateCacheEntryHeader header; + DxvkStateCacheEntryData data; + Sha1Hash hash; + + if (!stream.read(reinterpret_cast(&header), sizeof(header)) + || !stream.read(reinterpret_cast(&hash), sizeof(hash)) + || !data.readFromStream(stream, header.entrySize)) + return false; + + // Validate hash, skip entry if invalid + if (hash != data.computeHash()) + return false; + + // Read shader hashes + VkShaderStageFlags stageMask = VkShaderStageFlags(header.stageMask); + auto keys = &entry.shaders.vs; + + for (uint32_t i = 0; i < 6; i++) { + if (stageMask & VkShaderStageFlagBits(1 << i)) + data.read(keys[i]); + else + keys[i] = g_nullShaderKey; + } + + if (stageMask & VK_SHADER_STAGE_COMPUTE_BIT) { + if (!data.read(entry.cpState.bsBindingMask)) + return false; + } else { + // Read packed render pass format + uint8_t sampleCount = 0; + uint8_t imageFormat = 0; + uint8_t imageLayout = 0; + + if (!data.read(sampleCount) + || !data.read(imageFormat) + || !data.read(imageLayout)) + return false; + + entry.format.sampleCount = VkSampleCountFlagBits(sampleCount); + entry.format.depth.format = VkFormat(imageFormat); + entry.format.depth.layout = VkImageLayout(imageLayout); + + for (uint32_t i = 0; i < MaxNumRenderTargets; i++) { + if (!data.read(imageFormat) + || !data.read(imageLayout)) + return false; + + entry.format.color[i].format = VkFormat(imageFormat); + entry.format.color[i].layout = VkImageLayout(imageLayout); + } + + // Read common pipeline state + if (!data.read(entry.gpState.bsBindingMask) + || !data.read(entry.gpState.ia) + || !data.read(entry.gpState.il) + || !data.read(entry.gpState.rs) + || !data.read(entry.gpState.ms) + || !data.read(entry.gpState.ds) + || !data.read(entry.gpState.om) + || !data.read(entry.gpState.dsFront) + || !data.read(entry.gpState.dsBack)) + return false; + + if (entry.gpState.il.attributeCount() > MaxNumVertexAttributes + || entry.gpState.il.bindingCount() > MaxNumVertexBindings) + return false; + + // Read render target swizzles + for (uint32_t i = 0; i < MaxNumRenderTargets; i++) { + if (!data.read(entry.gpState.omSwizzle[i])) + return false; + } + + // Read render target blend info + for (uint32_t i = 0; i < MaxNumRenderTargets; i++) { + if (!data.read(entry.gpState.omBlend[i])) + return false; + } + + // Read defined vertex attributes + for (uint32_t i = 0; i < entry.gpState.il.attributeCount(); i++) { + if (!data.read(entry.gpState.ilAttributes[i])) + return false; + } + + // Read defined vertex bindings + for (uint32_t i = 0; i < entry.gpState.il.bindingCount(); i++) { + if (!data.read(entry.gpState.ilBindings[i])) + return false; + } + } + + // 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)) + return false; + + for (uint32_t i = 0; i < MaxNumSpecConstants; i++) { + if (specConstantMask & (1 << i)) { + if (!data.read(sc.specConstants[i])) + return false; + } + } + + return true; + } + + void DxvkStateCache::writeCacheEntry( std::ostream& stream, DxvkStateCacheEntry& entry) const { - entry.hash = Sha1Hash::compute(entry); + DxvkStateCacheEntryData data; + VkShaderStageFlags stageMask = 0; - auto data = reinterpret_cast(&entry); - auto size = sizeof(DxvkStateCacheEntry); + // Write shader hashes + auto keys = &entry.shaders.vs; - stream.write(data, size); + for (uint32_t i = 0; i < 6; i++) { + if (!keys[i].eq(g_nullShaderKey)) { + stageMask |= VkShaderStageFlagBits(1 << i); + data.write(keys[i]); + } + } + + if (stageMask & VK_SHADER_STAGE_COMPUTE_BIT) { + // Nothing else here to write out + data.write(entry.cpState.bsBindingMask); + } else { + // Pack render pass format + data.write(uint8_t(entry.format.sampleCount)); + data.write(uint8_t(entry.format.depth.format)); + data.write(uint8_t(entry.format.depth.layout)); + + for (uint32_t i = 0; i < MaxNumRenderTargets; i++) { + data.write(uint8_t(entry.format.color[i].format)); + data.write(uint8_t(entry.format.color[i].layout)); + } + + // Write out common pipeline state + data.write(entry.gpState.bsBindingMask); + data.write(entry.gpState.ia); + data.write(entry.gpState.il); + data.write(entry.gpState.rs); + data.write(entry.gpState.ms); + data.write(entry.gpState.ds); + data.write(entry.gpState.om); + data.write(entry.gpState.dsFront); + data.write(entry.gpState.dsBack); + + // Write out render target swizzles and blend info + for (uint32_t i = 0; i < MaxNumRenderTargets; i++) + data.write(entry.gpState.omSwizzle[i]); + + for (uint32_t i = 0; i < MaxNumRenderTargets; i++) + data.write(entry.gpState.omBlend[i]); + + // Write out input layout for defined attributes + for (uint32_t i = 0; i < entry.gpState.il.attributeCount(); i++) + data.write(entry.gpState.ilAttributes[i]); + + 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; + + uint32_t specConstantMask = 0; + + for (uint32_t i = 0; i < MaxNumSpecConstants; i++) + specConstantMask |= sc.specConstants[i] ? (1 << i) : 0; + + data.write(specConstantMask); + + for (uint32_t i = 0; i < MaxNumSpecConstants; i++) { + if (specConstantMask & (1 << i)) + data.write(sc.specConstants[i]); + } + + // General layout: header -> hash -> data + DxvkStateCacheEntryHeader header; + header.stageMask = uint8_t(stageMask); + header.entrySize = data.size(); + + Sha1Hash hash = data.computeHash(); + + stream.write(reinterpret_cast(&header), sizeof(header)); + stream.write(reinterpret_cast(&hash), sizeof(hash)); + stream.write(data.data(), data.size()); stream.flush(); } diff --git a/src/dxvk/dxvk_state_cache.h b/src/dxvk/dxvk_state_cache.h index 5b062148..f938efdd 100644 --- a/src/dxvk/dxvk_state_cache.h +++ b/src/dxvk/dxvk_state_cache.h @@ -143,6 +143,11 @@ namespace dxvk { std::istream& stream, DxvkStateCacheHeader& header) const; + bool readCacheEntryV7( + uint32_t version, + std::istream& stream, + DxvkStateCacheEntry& entry) const; + bool readCacheEntry( uint32_t version, std::istream& stream, diff --git a/src/dxvk/dxvk_state_cache_types.h b/src/dxvk/dxvk_state_cache_types.h index 6b2634c6..d5cfbdc9 100644 --- a/src/dxvk/dxvk_state_cache_types.h +++ b/src/dxvk/dxvk_state_cache_types.h @@ -52,8 +52,8 @@ namespace dxvk { */ struct DxvkStateCacheHeader { char magic[4] = { 'D', 'X', 'V', 'K' }; - uint32_t version = 7; - uint32_t entrySize = sizeof(DxvkStateCacheEntry); + uint32_t version = 8; + uint32_t entrySize = 0; /* no longer meaningful */ }; static_assert(sizeof(DxvkStateCacheHeader) == 12);