From f32200b6680727ff4b9b35fbc24393297694cacb Mon Sep 17 00:00:00 2001 From: Philip Rebohle Date: Thu, 4 Apr 2019 02:46:41 +0200 Subject: [PATCH] [spirv] Implement in-memory compression for shader modules --- src/spirv/meson.build | 1 + src/spirv/spirv_code_buffer.cpp | 6 ++ src/spirv/spirv_code_buffer.h | 9 +++ src/spirv/spirv_compression.cpp | 110 ++++++++++++++++++++++++++++++++ src/spirv/spirv_compression.h | 36 +++++++++++ src/spirv/spirv_include.h | 1 + 6 files changed, 163 insertions(+) create mode 100644 src/spirv/spirv_compression.cpp create mode 100644 src/spirv/spirv_compression.h diff --git a/src/spirv/meson.build b/src/spirv/meson.build index 70e6ae7c4..7c180f984 100644 --- a/src/spirv/meson.build +++ b/src/spirv/meson.build @@ -1,5 +1,6 @@ spirv_src = files([ 'spirv_code_buffer.cpp', + 'spirv_compression.cpp', 'spirv_module.cpp', ]) diff --git a/src/spirv/spirv_code_buffer.cpp b/src/spirv/spirv_code_buffer.cpp index 7cc3b4342..327f359db 100644 --- a/src/spirv/spirv_code_buffer.cpp +++ b/src/spirv/spirv_code_buffer.cpp @@ -9,6 +9,12 @@ namespace dxvk { SpirvCodeBuffer::~SpirvCodeBuffer() { } + SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size) + : m_ptr(size) { + m_code.resize(size); + } + + SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size, const uint32_t* data) : m_ptr(size) { m_code.resize(size); diff --git a/src/spirv/spirv_code_buffer.h b/src/spirv/spirv_code_buffer.h index b1d248284..81ba24da6 100644 --- a/src/spirv/spirv_code_buffer.h +++ b/src/spirv/spirv_code_buffer.h @@ -21,6 +21,7 @@ namespace dxvk { public: SpirvCodeBuffer(); + explicit SpirvCodeBuffer(uint32_t size); SpirvCodeBuffer(uint32_t size, const uint32_t* data); SpirvCodeBuffer(std::istream& stream); @@ -37,6 +38,14 @@ namespace dxvk { const uint32_t* data() const { return m_code.data(); } uint32_t* data() { return m_code.data(); } + /** + * \brief Code size, in dwords + * \returns Code size, in dwords + */ + uint32_t dwords() const { + return m_code.size(); + } + /** * \brief Code size, in bytes * \returns Code size, in bytes diff --git a/src/spirv/spirv_compression.cpp b/src/spirv/spirv_compression.cpp new file mode 100644 index 000000000..c3f8e90ae --- /dev/null +++ b/src/spirv/spirv_compression.cpp @@ -0,0 +1,110 @@ +#include "spirv_compression.h" + +namespace dxvk { + + SpirvCompressedBuffer::SpirvCompressedBuffer() + : m_size(0) { + + } + + + SpirvCompressedBuffer::SpirvCompressedBuffer( + const SpirvCodeBuffer& code) + : m_size(code.dwords()) { + const uint32_t* data = code.data(); + + // The compression works by eliminating leading null bytes + // from DWORDs, exploiting that SPIR-V IDs are consecutive + // integers that usually fall into the 16-bit range. For + // each DWORD, a two-bit integer is stored which indicates + // the number of bytes it takes in the compressed buffer. + // This way, it can achieve a compression ratio of ~50%. + m_mask.reserve((m_size + NumMaskWords - 1) / NumMaskWords); + m_code.reserve(m_size * 4); + + uint64_t dstWord = 0; + uint32_t dstShift = 0; + + for (uint32_t i = 0; i < m_size; i += NumMaskWords) { + uint64_t byteCounts = 0; + + for (uint32_t w = 0; w < NumMaskWords && i + w < m_size; w++) { + uint64_t word = data[i + w]; + uint64_t bytes = 0; + + if (word < (1 << 8)) bytes = 0; + else if (word < (1 << 16)) bytes = 1; + else if (word < (1 << 24)) bytes = 2; + else bytes = 3; + + byteCounts |= bytes << (2 * w); + + uint32_t bits = 8 * bytes + 8; + uint32_t rem = bit::pack(dstWord, dstShift, word, bits); + + if (unlikely(rem != 0)) { + m_code.push_back(dstWord); + + dstWord = 0; + dstShift = 0; + + bit::pack(dstWord, dstShift, word >> (bits - rem), rem); + } + } + + m_mask.push_back(byteCounts); + } + + if (dstShift) + m_code.push_back(dstWord); + + m_mask.shrink_to_fit(); + m_code.shrink_to_fit(); + } + + + SpirvCompressedBuffer::~SpirvCompressedBuffer() { + + } + + + SpirvCodeBuffer SpirvCompressedBuffer::decompress() const { + SpirvCodeBuffer code(m_size); + uint32_t* data = code.data(); + + if (m_size == 0) + return code; + + uint32_t maskIdx = 0; + uint32_t codeIdx = 0; + + uint64_t srcWord = m_code[codeIdx++]; + uint32_t srcShift = 0; + + for (uint32_t i = 0; i < m_size; i += NumMaskWords) { + uint64_t srcMask = m_mask[maskIdx++]; + + for (uint32_t w = 0; w < NumMaskWords && i + w < m_size; w++) { + uint32_t bits = 8 * ((srcMask & 3) + 1); + + uint64_t word = 0; + uint32_t rem = bit::unpack(word, srcWord, srcShift, bits); + + if (unlikely(rem != 0)) { + srcWord = m_code[codeIdx++]; + srcShift = 0; + + uint64_t tmp = 0; + bit::unpack(tmp, srcWord, srcShift, rem); + word |= tmp << (bits - rem); + } + + data[i + w] = word; + srcMask >>= 2; + } + } + + return code; + } + +} \ No newline at end of file diff --git a/src/spirv/spirv_compression.h b/src/spirv/spirv_compression.h new file mode 100644 index 000000000..7a1276c8e --- /dev/null +++ b/src/spirv/spirv_compression.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "spirv_code_buffer.h" + +namespace dxvk { + + /** + * \brief Compressed SPIR-V code buffer + * + * Implements a fast in-memory compression + * to keep memory footprint low. + */ + class SpirvCompressedBuffer { + constexpr static uint32_t NumMaskWords = 32; + public: + + SpirvCompressedBuffer(); + + SpirvCompressedBuffer( + const SpirvCodeBuffer& code); + + ~SpirvCompressedBuffer(); + + SpirvCodeBuffer decompress() const; + + private: + + uint32_t m_size; + std::vector m_mask; + std::vector m_code; + + }; + +} \ No newline at end of file diff --git a/src/spirv/spirv_include.h b/src/spirv/spirv_include.h index b38fbbd13..fca941816 100644 --- a/src/spirv/spirv_include.h +++ b/src/spirv/spirv_include.h @@ -5,6 +5,7 @@ #include "../util/util_error.h" #include "../util/util_flags.h" +#include "../util/util_likely.h" #include "../util/util_string.h" #include "../util/rc/util_rc.h"