code:
#include <array>
#include <cstring>

#include "spirv_code_buffer.h"

namespace dxvk {

  SpirvCodeBuffer:: SpirvCodeBuffer() { }
  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);
    std::memcpy(m_code.data(), data, size * sizeof(uint32_t));
  }

  SpirvCodeBuffer::SpirvCodeBuffer(std::istream& stream) {
    stream.ignore(std::numeric_limits<std::streamsize>::max());
    std::streamsize length = stream.gcount();
    stream.clear();
    stream.seekg(0, std::ios_base::beg);

    std::vector<char> buffer(length);
    stream.read(buffer.data(), length);
    buffer.resize(stream.gcount());

    m_code.resize(buffer.size() / sizeof(uint32_t));
    std::memcpy(reinterpret_cast<char*>(m_code.data()),
      buffer.data(), m_code.size() * sizeof(uint32_t));

    m_ptr = m_code.size();
  }

  uint32_t SpirvCodeBuffer::allocId() {
    constexpr size_t BoundIdsOffset = 3;

    if (m_code.size() <= BoundIdsOffset)
      return 0;

    return m_code[BoundIdsOffset]++;
  }

  void SpirvCodeBuffer::append(const SpirvCodeBuffer& other) {
    if (other.size() != 0) {
      const size_t size = m_code.size();
      m_code.resize(size + other.m_code.size());

            uint32_t* dst = this->m_code.data();
      const uint32_t* src = other.m_code.data();

      std::memcpy(dst + size, src, other.size());
      m_ptr += other.m_code.size();
    }
  }

  void SpirvCodeBuffer::putWord(uint32_t word) {
    m_code.insert(m_code.begin() + m_ptr, word);
    m_ptr += 1;
  }

  void SpirvCodeBuffer::putIns(spv::Op opCode, uint16_t wordCount) {
    this->putWord(
        (static_cast<uint32_t>(opCode)    <<  0)
      | (static_cast<uint32_t>(wordCount) << 16));
  }

  void SpirvCodeBuffer::putInt32(uint32_t word) {
    this->putWord(word);
  }

  void SpirvCodeBuffer::putInt64(uint64_t value) {
    this->putWord(value >>  0);
    this->putWord(value >> 32);
  }

  void SpirvCodeBuffer::putFloat32(float value) {
    uint32_t tmp;
    static_assert(sizeof(tmp) == sizeof(value));
    std::memcpy(&tmp, &value, sizeof(value));
    this->putInt32(tmp);
  }

  void SpirvCodeBuffer::putFloat64(double value) {
    uint64_t tmp;
    static_assert(sizeof(tmp) == sizeof(value));
    std::memcpy(&tmp, &value, sizeof(value));
    this->putInt64(tmp);
  }

  void SpirvCodeBuffer::putStr(const char* str) {
    uint32_t word = 0;
    uint32_t nbit = 0;

    for (uint32_t i = 0; str[i] != '\0'; str++) {
      word |= (static_cast<uint32_t>(str[i]) & 0xFF) << nbit;

      if ((nbit += 8) == 32) {
        this->putWord(word);
        word = 0;
        nbit = 0;
      }
    }

    // Commit current word
    this->putWord(word);
  }

  void SpirvCodeBuffer::putHeader(uint32_t version, uint32_t boundIds) {
    this->putWord(spv::MagicNumber);
    this->putWord(version);
    this->putWord(0); // Generator
    this->putWord(boundIds);
    this->putWord(0); // Schema
  }

  void SpirvCodeBuffer::erase(size_t size) {
    m_code.erase(
      m_code.begin() + m_ptr,
      m_code.begin() + m_ptr + size);
  }

  uint32_t SpirvCodeBuffer::strLen(const char* str) {
    // Null-termination plus padding
    return (std::strlen(str) + 4) / 4;
  }

  void SpirvCodeBuffer::store(std::ostream& stream) const {
    stream.write(
      reinterpret_cast<const char*>(m_code.data()),
      sizeof(uint32_t) * m_code.size());
  }

}


code:
#pragma once

#include <spirv/spirv.hpp>

#include <iostream>
#include <utility>
#include <vector>

#include "spirv_instruction.h"

namespace dxvk {

  /**
   * \brief SPIR-V code buffer
   *
   * Helper class for generating SPIR-V shaders.
   * Stores arbitrary SPIR-V instructions in a
   * format that can be read by Vulkan drivers.
   */
  class SpirvCodeBuffer {

  public:

    SpirvCodeBuffer();
    explicit SpirvCodeBuffer(uint32_t size);
    SpirvCodeBuffer(uint32_t size, const uint32_t* data);
    SpirvCodeBuffer(std::istream& stream);

    template<size_t N>
    SpirvCodeBuffer(const uint32_t (&data)[N])
    : SpirvCodeBuffer(N, data) { }

    ~SpirvCodeBuffer();

    /**
     * \brief Code data
     * \returns Code data
     */
    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
     */
    size_t size() const {
      return m_code.size() * sizeof(uint32_t);
    }

    /**
     * \brief Begin instruction iterator
     *
     * Points to the first instruction in the instruction
     * block. The header, if any, will be skipped over.
     * \returns Instruction iterator
     */
    SpirvInstructionIterator begin() {
      return SpirvInstructionIterator(
        m_code.data(), 0, m_code.size());
    }

    /**
     * \brief End instruction iterator
     *
     * Points to the end of the instruction block.
     * \returns Instruction iterator
     */
    SpirvInstructionIterator end() {
      return SpirvInstructionIterator(nullptr, 0, 0);
    }

    /**
     * \brief Allocates a new ID
     *
     * Returns a new valid ID and increments the
     * maximum ID count stored in the header.
     * \returns The new SPIR-V ID
     */
    uint32_t allocId();

    /**
     * \brief Merges two code buffers
     *
     * This is useful to generate declarations or
     * the SPIR-V header at the same time as the
     * code when doing so in advance is impossible.
     * \param [in] other Code buffer to append
     */
    void append(const SpirvCodeBuffer& other);

    /**
     * \brief Appends an 32-bit word to the buffer
     * \param [in] word The word to append
     */
    void putWord(uint32_t word);

    /**
     * \brief Appends an instruction word to the buffer
     *
     * Adds a single word containing both the word count
     * and the op code number for a single instruction.
     * \param [in] opCode Operand code
     * \param [in] wordCount Number of words
     */
    void putIns(spv::Op opCode, uint16_t wordCount);

    /**
     * \brief Appends a 32-bit integer to the buffer
     * \param [in] value The number to add
     */
    void putInt32(uint32_t word);

    /**
     * \brief Appends a 64-bit integer to the buffer
     *
     * A 64-bit integer will take up two 32-bit words.
     * \param [in] value 64-bit value to add
     */
    void putInt64(uint64_t value);

    /**
     * \brief Appends a 32-bit float to the buffer
     * \param [in] value The number to add
     */
    void putFloat32(float value);

    /**
     * \brief Appends a 64-bit float to the buffer
     * \param [in] value The number to add
     */
    void putFloat64(double value);

    /**
     * \brief Appends a literal string to the buffer
     * \param [in] str String to append to the buffer
     */
    void putStr(const char* str);

    /**
     * \brief Adds the header to the buffer
     *
     * \param [in] version SPIR-V version
     * \param [in] boundIds Number of bound IDs
     */
    void putHeader(uint32_t version, uint32_t boundIds);

    /**
     * \brief Erases given number of dwords
     *
     * Removes data from the code buffer, starting
     * at the current insertion offset.
     * \param [in] size Number of words to remove
     */
    void erase(size_t size);

    /**
     * \brief Computes length of a literal string
     *
     * \param [in] str The string to check
     * \returns Number of words consumed by a string
     */
    uint32_t strLen(const char* str);

    /**
     * \brief Stores the SPIR-V module to a stream
     *
     * The ability to save modules to a file
     * exists mostly for debugging purposes.
     * \param [in] stream Output stream
     */
    void store(std::ostream& stream) const;

    /**
     * \brief Retrieves current insertion pointer
     *
     * Sometimes it may be necessay to insert code into the
     * middle of the stream rather than appending it. This
     * retrieves the current function pointer. Note that the
     * pointer will become invalid if any code is inserted
     * before the current pointer location.
     * \returns Current instruction pointr
     */
    size_t getInsertionPtr() const {
      return m_ptr;
    }

    /**
     * \brief Sets insertion pointer to a specific value
     *
     * Sets the insertion pointer to a value that was
     * previously retrieved by \ref getInsertionPtr.
     * \returns Current instruction pointr
     */
    void beginInsertion(size_t ptr) {
      m_ptr = ptr;
    }

    /**
     * \brief Sets insertion pointer to the end
     *
     * After this call, new instructions will be
     * appended to the stream. In other words,
     * this will restore default behaviour.
     * \returns Previous instruction pointer
     */
    size_t endInsertion() {
      return std::exchange(m_ptr, m_code.size());
    }

  private:

    std::vector<uint32_t> m_code;
    size_t m_ptr = 0;

  };

}

repo_name: orochi/dxvk


code:
#include "spirv_compression.h"

namespace dxvk {

  SpirvCompressedBuffer::SpirvCompressedBuffer()
  : m_size(0) {

  }

  SpirvCompressedBuffer::SpirvCompressedBuffer(SpirvCodeBuffer& code)
  : m_size(code.dwords()) {
    // The compression (detailed below) achieves roughly 55% of the
    // original size on average and is very consistent, so an initial
    // estimate of roughly 58% will be accurate most of the time.
    const uint32_t* data = code.data();
    m_code.reserve((m_size * 75) / 128);

    std::array<uint32_t, 16> block;
    uint32_t blockMask = 0;
    uint32_t blockOffset = 0;

    // The algorithm used is a simple variable-to-fixed compression that
    // encodes up to two consecutive SPIR-V tokens into one DWORD using
    // a small number of different encodings. While not achieving great
    // compression ratios, the main goal is to allow decompression code
    // to be fast, with short dependency chains.
    // Compressed tokens are stored in blocks of 16 DWORDs, each preceeded
    // by a single DWORD which stores the layout for each DWORD, two bits
    // each. The supported layouts, are as follows:
    // 0x0: 1x 32-bit;  0x1: 1x 20-bit + 1x 12-bit
    // 0x2: 2x 16-bit;  0x3: 1x 12-bit + 1x 20-bit
    // These layouts are chosen to allow reasonably efficient encoding of
    // opcode tokens, which usually fit into 20 bits, followed by type IDs,
    // which tend to be low as well since most types are defined early.
    for (size_t i = 0; i < m_size; ) {
      if (likely(i + 1 < m_size)) {
        uint32_t a = data[i];
        uint32_t b = data[i + 1];
        uint32_t schema;
        uint32_t encode;

        if (std::max(a, b) < (1u << 16)) {
          schema = 0x2;
          encode = a | (b << 16);
        } else if (a < (1u << 20) && b < (1u << 12)) {
          schema = 0x1;
          encode = a | (b << 20);
        } else if (a < (1u << 12) && b < (1u << 20)) {
          schema = 0x3;
          encode = a | (b << 12);
        } else {
          schema = 0x0;
          encode = a;
        }

        block[blockOffset] = encode;
        blockMask |= schema << (blockOffset << 1);
        blockOffset += 1;

        i += schema ? 2 : 1;
      } else {
        block[blockOffset] = data[i++];
        blockOffset += 1;
      }

      if (unlikely(blockOffset == 16) || unlikely(i == m_size)) {
        m_code.insert(m_code.end(), blockMask);
        m_code.insert(m_code.end(), block.begin(), block.begin() + blockOffset);

        blockMask = 0;
        blockOffset = 0;
      }
    }

    // Only shrink the array if we have lots of overhead for some reason.
    // This should only happen on shaders where our initial estimate was
    // too small. In general, we want to avoid reallocation here.
    if (m_code.capacity() > (m_code.size() * 10) / 9)
      m_code.shrink_to_fit();
  }

  SpirvCompressedBuffer::~SpirvCompressedBuffer() {

  }

  SpirvCodeBuffer SpirvCompressedBuffer::decompress() const {
    SpirvCodeBuffer code(m_size);
    uint32_t* data = code.data();

    uint32_t srcOffset = 0;
    uint32_t dstOffset = 0;

    constexpr uint32_t shiftAmounts = 0x0c101420;

    while (dstOffset < m_size) {
      uint32_t blockMask = m_code[srcOffset];

      for (uint32_t i = 0; i < 16 && dstOffset < m_size; i++) {
        // Use 64-bit integers for some of the operands so we can
        // shift by 32 bits and not handle it as a special cases
        uint32_t schema = (blockMask >> (i << 1)) & 0x3;
        uint32_t shift  = (shiftAmounts >> (schema << 3)) & 0xff;
        uint64_t mask   = ~(~0ull << shift);
        uint64_t encode = m_code[srcOffset + i + 1];

        data[dstOffset] = encode & mask;

        if (likely(schema))
          data[dstOffset + 1] = encode >> shift;

        dstOffset += schema ? 2 : 1;
      }

      srcOffset += 17;
    }

    return code;
  }

}


code:
#pragma once

#include <vector>

#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 {

  public:

    SpirvCompressedBuffer();

    SpirvCompressedBuffer(SpirvCodeBuffer& code);

    ~SpirvCompressedBuffer();

    SpirvCodeBuffer decompress() const;

  private:

    size_t                m_size;
    std::vector<uint32_t> m_code;

    void encodeDword(uint32_t dw);

    uint32_t decodeDword(size_t& offset) const;

  };

}


code:
#pragma once

#include "../util/log/log.h"
#include "../util/log/log_debug.h"

#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"
#include "../util/rc/util_rc_ptr.h"


code:
#pragma once

#include <spirv/spirv.hpp>
#include <spirv/GLSL.std.450.hpp>

#include "spirv_include.h"

namespace dxvk {

  /**
   * \brief SPIR-V instruction
   *
   * Helps parsing a single instruction, providing
   * access to the op code, instruction length and
   * instruction arguments.
   */
  class SpirvInstruction {

  public:

    SpirvInstruction() { }
    SpirvInstruction(uint32_t* code, uint32_t offset, uint32_t length)
    : m_code(code), m_offset(offset), m_length(length) { }

    /**
     * \brief SPIR-V Op code
     * \returns The op code
     */
    spv::Op opCode() const {
      return static_cast<spv::Op>(
        this->arg(0) & spv::OpCodeMask);
    }

    /**
     * \brief Instruction length
     * \returns Number of DWORDs
     */
    uint32_t length() const {
      return this->arg(0) >> spv::WordCountShift;
    }

    /**
     * \brief Instruction offset
     * \returns Offset in DWORDs
     */
    uint32_t offset() const {
      return m_offset;
    }

    /**
     * \brief Argument value
     *
     * Retrieves an argument DWORD. Note that some instructions
     * take 64-bit arguments which require more than one DWORD.
     * Arguments start at index 1. Calling this method with an
     * argument ID of 0 will return the opcode token.
     * \param [in] idx Argument index, starting at 1
     * \returns The argument value
     */
    uint32_t arg(uint32_t idx) const {
      const uint32_t index = m_offset + idx;
      return index < m_length ? m_code[index] : 0;
    }

    /**
     * \brief Argument string
     *
     * Retrieves a pointer to a UTF-8-encoded string.
     * \param [in] idx Argument index, starting at 1
     * \returns Pointer to the literal string
     */
    const char* chr(uint32_t idx) const {
      const uint32_t index = m_offset + idx;
      return index < m_length ? reinterpret_cast<const char*>(&m_code[index]) : nullptr;
    }

    /**
     * \brief Changes the value of an argument
     *
     * \param [in] idx Argument index, starting at 1
     * \param [in] word New argument word
     */
    void setArg(uint32_t idx, uint32_t word) const {
      if (m_offset + idx < m_length)
        m_code[m_offset + idx] = word;
    }

  private:

    uint32_t* m_code   = nullptr;
    uint32_t  m_offset = 0;
    uint32_t  m_length = 0;

  };

  /**
   * \brief SPIR-V instruction iterator
   *
   * Convenient iterator that can be used
   * to process raw SPIR-V shader code.
   */
  class SpirvInstructionIterator {

  public:

    SpirvInstructionIterator() { }
    SpirvInstructionIterator(uint32_t* code, uint32_t offset, uint32_t length)
    : m_code  (length != 0 ? code   : nullptr),
      m_offset(length != 0 ? offset : 0),
      m_length(length) {
      if ((length >= 5) && (offset == 0) && (m_code[0] == spv::MagicNumber))
        this->advance(5);
    }

    SpirvInstructionIterator& operator ++ () {
      this->advance(SpirvInstruction(m_code, m_offset, m_length).length());
      return *this;
    }

    SpirvInstructionIterator operator ++ (int) {
      SpirvInstructionIterator result = *this;
      this->advance(SpirvInstruction(m_code, m_offset, m_length).length());
      return result;
    }

    SpirvInstruction operator * () const {
      return SpirvInstruction(m_code, m_offset, m_length);
    }

    bool operator == (const SpirvInstructionIterator& other) const {
      return this->m_code   == other.m_code
          && this->m_offset == other.m_offset
          && this->m_length == other.m_length;
    }

    bool operator != (const SpirvInstructionIterator& other) const {
      return this->m_code   != other.m_code
          || this->m_offset != other.m_offset
          || this->m_length != other.m_length;
    }

  private:

    uint32_t* m_code   = nullptr;
    uint32_t  m_offset = 0;
    uint32_t  m_length = 0;

    void advance(uint32_t n) {
      if (m_offset + n < m_length) {
        m_offset += n;
      } else {
        m_code   = nullptr;
        m_offset = 0;
        m_length = 0;
      }
    }

  };

}