code:
#pragma once

#include "dxvk_limits.h"
#include "dxvk_shader.h"

namespace dxvk {

  /**
   * \briefS Specialization constant entry
   *
   * Used to pass a list of user-defined
   * specialization constants to shaders.
   */
  struct DxvkSpecConstant {
    uint32_t specId;
    uint32_t value;
  };

  /**
   * \brief Specialization constant info
   *
   * Accumulates specialization constant data for
   * constants that use non-default values.
   */
  class DxvkSpecConstants {

  public:

    DxvkSpecConstants();

    ~DxvkSpecConstants();

    /**
     * \brief Sets specialization constant value
     *
     * If the given value is different from the constant's
     * default value, this will store the new value and add
     * a map entry so that it gets applied properly. Each
     * constant may only be set once.
     * \param [in] specId Specialization constant ID
     * \param [in] value Specialization constant value
     * \param [in] defaultValue Default value
     */
    template<typename T>
    void set(uint32_t specId, T value, T defaultValue) {
      if (value != defaultValue)
        setAsUint32(specId, uint32_t(value));
    }

    /**
     * \brief Sets specialization constant value
     *
     * Always passes the constant value to the driver.
     * \param [in] specId Specialization constant ID
     * \param [in] value Specialization constant value
     */
    template<typename T>
    void set(uint32_t specId, T value) {
      setAsUint32(specId, uint32_t(value));
    }

    /**
     * \brief Generates specialization info structure
     * \returns Specialization info for shader module
     */
    VkSpecializationInfo getSpecInfo() const;

  private:

    std::vector<uint32_t>                 m_data = { };
    std::vector<VkSpecializationMapEntry> m_map  = { };

    void setAsUint32(uint32_t specId, uint32_t value);

  };

  /**
   * \brief Computes specialization constant ID
   *
   * Computest the specId to use within shaders
   * for a given pipeline specialization constant.
   * \param [in] index Spec constant index
   * \returns Specialization constant ID
   */
  inline uint32_t getSpecId(uint32_t index) {
    return uint32_t(DxvkSpecConstantId::FirstPipelineConstant) + index;
  }

}


code:
#include "dxvk_device.h"
#include "dxvk_staging.h"

namespace dxvk {

  DxvkStagingBuffer::DxvkStagingBuffer(
    const Rc<DxvkDevice>&     device,
          VkDeviceSize        size)
  : m_device(device), m_offset(0), m_size(size) {

  }

  DxvkStagingBuffer::~DxvkStagingBuffer() {

  }

  DxvkBufferSlice DxvkStagingBuffer::alloc(VkDeviceSize align, VkDeviceSize size) {
    DxvkBufferCreateInfo info;
    info.size   = size;
    info.usage  = VK_BUFFER_USAGE_TRANSFER_SRC_BIT
                | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT
                | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
    info.stages = VK_PIPELINE_STAGE_TRANSFER_BIT
                | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
                | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
    info.access = VK_ACCESS_TRANSFER_READ_BIT
                | VK_ACCESS_SHADER_READ_BIT;

    VkDeviceSize alignedSize = dxvk::align(size, align);
    VkDeviceSize alignedOffset = dxvk::align(m_offset, align);

    if (2 * alignedSize > m_size) {
      return DxvkBufferSlice(m_device->createBuffer(info,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT));
    }

    if (alignedOffset + alignedSize > m_size || m_buffer == nullptr) {
      info.size = m_size;

      // Free resources first if possible, in some rare
      // situations this may help avoid a memory allocation.
      m_buffer = nullptr;
      m_buffer = m_device->createBuffer(info,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
      alignedOffset = 0;
    }

    DxvkBufferSlice slice(m_buffer, alignedOffset, size);
    m_offset = alignedOffset + alignedSize;
    return slice;
  }

  void DxvkStagingBuffer::reset() {
    m_buffer = nullptr;
    m_offset = 0;
  }

}

repo_name: orochi/dxvk


code:
#pragma once

#include <queue>

#include "dxvk_buffer.h"

namespace dxvk {

  class DxvkDevice;

  /**
   * \brief Staging buffer
   *
   * Provides a simple linear staging buffer
   * allocator for data uploads.
   */
  class DxvkStagingBuffer {

  public:

    /**
     * \brief Creates staging buffer
     *
     * \param [in] device DXVK device
     * \param [in] size Buffer size
     */
    DxvkStagingBuffer(
      const Rc<DxvkDevice>&     device,
            VkDeviceSize        size);

    /**
     * \brief Frees staging buffer
     */
    ~DxvkStagingBuffer();

    /**
     * \brief Allocates staging buffer memory
     *
     * Tries to suballocate from existing buffer,
     * or creates a new buffer if necessary.
     * \param [in] align Minimum alignment
     * \param [in] size Number of bytes to allocate
     * \returns Allocated slice
     */
    DxvkBufferSlice alloc(VkDeviceSize align, VkDeviceSize size);

    /**
     * \brief Resets staging buffer and allocator
     */
    void reset();

  private:

    Rc<DxvkDevice>  m_device;
    Rc<DxvkBuffer>  m_buffer;
    VkDeviceSize    m_offset;
    VkDeviceSize    m_size;

  };

}


code:
#include "dxvk_device.h"
#include "dxvk_pipemanager.h"
#include "dxvk_state_cache.h"

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<typename T>
    bool read(T& data, uint32_t version) {
      return read(data);
    }

    bool read(DxvkBindingMask& data, uint32_t version) {
      if (version < 9) {
        DxvkBindingMaskV8 v8;

        if (!read(v8))
          return false;

        data = v8.convert();
        return true;
      }

      return read(data);
    }

    bool read(DxvkIlBinding& data, uint32_t version) {
      if (version < 10) {
        DxvkIlBindingV9 v9;

        if (!read(v9))
          return false;

        data = v9.convert();
        return true;
      }

      return read(data);
    }

    template<typename T>
    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<typename T>
    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<typename T>
  bool readCacheEntryTyped(std::istream& stream, T& entry) {
    auto data = reinterpret_cast<char*>(&entry);
    auto size = sizeof(entry);

    if (!stream.read(data, size))
      return false;

    Sha1Hash expectedHash = std::exchange(entry.hash, g_nullHash);
    Sha1Hash computedHash = Sha1Hash::compute(entry);
    return expectedHash == computedHash;
  }

  bool DxvkStateCacheKey::eq(const DxvkStateCacheKey& key) const {
    return this->vs.eq(key.vs)
        && 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);
  }

  size_t DxvkStateCacheKey::hash() const {
    DxvkHashState hash;
    hash.add(this->vs.hash());
    hash.add(this->tcs.hash());
    hash.add(this->tes.hash());
    hash.add(this->gs.hash());
    hash.add(this->fs.hash());
    hash.add(this->cs.hash());
    return hash;
  }

  DxvkStateCache::DxvkStateCache(
          DxvkDevice*           device,
          DxvkPipelineManager*  pipeManager,
          DxvkRenderPassPool*   passManager)
  : m_device      (device),
    m_pipeManager (pipeManager),
    m_passManager (passManager) {
    bool newFile = !readCacheFile();

    if (newFile) {
      Logger::warn("DXVK: Creating new state cache file");

      // Start with an empty file
      std::ofstream file(getCacheFileName().c_str(),
        std::ios_base::binary |
        std::ios_base::trunc);

      if (!file && env::createDirectory(getCacheDir())) {
        file = std::ofstream(getCacheFileName().c_str(),
          std::ios_base::binary |
          std::ios_base::trunc);
      }

      // Write header with the current version number
      DxvkStateCacheHeader header;

      auto data = reinterpret_cast<const char*>(&header);
      auto size = sizeof(header);

      file.write(data, size);

      // Write all valid entries to the cache file in
      // case we're recovering a corrupted cache file
      for (auto& e : m_entries)
        writeCacheEntry(file, e);
    }
  }

  DxvkStateCache::~DxvkStateCache() {
    this->stopWorkerThreads();
  }

  void DxvkStateCache::addGraphicsPipeline(
    const DxvkStateCacheKey&              shaders,
    const DxvkGraphicsPipelineStateInfo&  state,
    const DxvkRenderPassFormat&           format) {
    if (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.format.eq(format) && entry.gpState == state)
        return;
    }

    // Queue a job to write this pipeline to the cache
    std::unique_lock<dxvk::mutex> lock(m_writerLock);

    m_writerQueue.push({ shaders, state,
      DxvkComputePipelineStateInfo(),
      format, g_nullHash });
    m_writerCond.notify_one();

    createWriter();
  }

  void DxvkStateCache::addComputePipeline(
    const DxvkStateCacheKey&              shaders,
    const DxvkComputePipelineStateInfo&   state) {
    if (shaders.cs.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)
        return;
    }

    // Queue a job to write this pipeline to the cache
    std::unique_lock<dxvk::mutex> lock(m_writerLock);

    m_writerQueue.push({ shaders,
      DxvkGraphicsPipelineStateInfo(), state,
      DxvkRenderPassFormat(), g_nullHash });
    m_writerCond.notify_one();

    createWriter();
  }

  void DxvkStateCache::registerShader(const Rc<DxvkShader>& shader) {
    DxvkShaderKey key = shader->getShaderKey();

    if (key.eq(g_nullShaderKey))
      return;

    // Add the shader so we can look it up by its key
    std::unique_lock<dxvk::mutex> entryLock(m_entryLock);
    m_shaderMap.insert({ key, shader });

    // Deferred lock, don't stall workers unless we have to
    std::unique_lock<dxvk::mutex> workerLock;

    auto pipelines = m_pipelineMap.equal_range(key);

    for (auto p = pipelines.first; p != pipelines.second; p++) {
      WorkerItem item;

      if (!getShaderByKey(p->second.vs,  item.gp.vs)
       || !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))
        continue;

      if (!workerLock)
        workerLock = std::unique_lock<dxvk::mutex>(m_workerLock);

      m_workerQueue.push(item);
    }

    if (workerLock) {
      m_workerCond.notify_all();
      createWorkers();
    }
  }

  void DxvkStateCache::stopWorkerThreads() {
    { std::lock_guard<dxvk::mutex> workerLock(m_workerLock);
      std::lock_guard<dxvk::mutex> writerLock(m_writerLock);

      if (m_stopThreads.exchange(true))
        return;

      m_workerCond.notify_all();
      m_writerCond.notify_all();
    }

    for (auto& worker : m_workerThreads)
      worker.join();

    if (m_writerThread.joinable())
      m_writerThread.join();
  }

  DxvkShaderKey DxvkStateCache::getShaderKey(const Rc<DxvkShader>& shader) const {
    return shader != nullptr ? shader->getShaderKey() : g_nullShaderKey;
  }

  bool DxvkStateCache::getShaderByKey(
    const DxvkShaderKey&            key,
          Rc<DxvkShader>&           shader) const {
    if (key.eq(g_nullShaderKey))
      return true;

    auto entry = m_shaderMap.find(key);
    if (entry == m_shaderMap.end())
      return false;

    shader = entry->second;
    return true;
  }

  void DxvkStateCache::mapPipelineToEntry(
    const DxvkStateCacheKey&        key,
          size_t                    entryId) {
    m_entryMap.insert({ key, entryId });
  }

  void DxvkStateCache::mapShaderToPipeline(
    const DxvkShaderKey&            shader,
    const DxvkStateCacheKey&        key) {
    if (!shader.eq(g_nullShaderKey))
      m_pipelineMap.insert({ shader, key });
  }

  void DxvkStateCache::compilePipelines(const WorkerItem& item) {
    DxvkStateCacheKey key;
    key.vs  = getShaderKey(item.gp.vs);
    key.tcs = getShaderKey(item.gp.tcs);
    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);

      for (auto e = entries.first; e != entries.second; e++) {
        const auto& entry = m_entries[e->second];

        if (m_passManager->validateRenderPassFormat(entry.format)) {
          auto rp = m_passManager->getRenderPass(entry.format);
          pipeline->compilePipeline(entry.gpState, rp);
        }
      }
    } 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];
        pipeline->compilePipeline(entry.cpState);
      }
    }
  }

  bool DxvkStateCache::readCacheFile() {
    // Open state file and just fail if it doesn't exist
    std::ifstream ifile(getCacheFileName().c_str(), std::ios_base::binary);

    if (!ifile) {
      Logger::warn("DXVK: No state cache file found");
      return false;
    }

    // The header stores the state cache version,
    // we need to regenerate it if it's outdated
    DxvkStateCacheHeader newHeader;
    DxvkStateCacheHeader curHeader;

    if (!readCacheHeader(ifile, curHeader)) {
      Logger::warn("DXVK: Failed to read state cache header");
      return false;
    }

    // Struct size hasn't changed between v2 and v4
    size_t expectedSize = newHeader.entrySize;

    if (curHeader.version <= 4)
      expectedSize = sizeof(DxvkStateCacheEntryV4);
    else if (curHeader.version <= 5)
      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");
      return false;
    }

    // Discard caches of unsupported versions
    if (curHeader.version < 2 || curHeader.version > newHeader.version) {
      Logger::warn("DXVK: State cache version not supported");
      return false;
    }

    // Notify user about format conversion
    if (curHeader.version != newHeader.version)
      Logger::warn(str::format("DXVK: Updating state cache version to v", newHeader.version));

    // Read actual cache entries from the file.
    // If we encounter invalid entries, we should
    // regenerate the entire state cache file.
    uint32_t numInvalidEntries = 0;

    while (ifile) {
      DxvkStateCacheEntry entry;

      if (readCacheEntry(curHeader.version, ifile, entry)) {
        size_t entryId = m_entries.size();
        m_entries.push_back(entry);

        mapPipelineToEntry(entry.shaders, entryId);

        mapShaderToPipeline(entry.shaders.vs,  entry.shaders);
        mapShaderToPipeline(entry.shaders.tcs, entry.shaders);
        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;
      }
    }

    Logger::info(str::format(
      "DXVK: Read ", m_entries.size(),
      " valid state cache entries"));

    if (numInvalidEntries) {
      Logger::warn(str::format(
        "DXVK: Skipped ", numInvalidEntries,
        " invalid state cache entries"));
      return false;
    }

    // Rewrite entire state cache if it is outdated
    return curHeader.version == newHeader.version;
  }

  bool DxvkStateCache::readCacheHeader(
          std::istream&             stream,
          DxvkStateCacheHeader&     header) const {
    DxvkStateCacheHeader expected;

    auto data = reinterpret_cast<char*>(&header);
    auto size = sizeof(header);

    if (!stream.read(data, size))
      return false;

    for (uint32_t i = 0; i < 4; i++) {
      if (expected.magic[i] != header.magic[i])
        return false;
    }

    return true;
  }

  bool DxvkStateCache::readCacheEntryV7(
          uint32_t                  version,
          std::istream&             stream,
          DxvkStateCacheEntry&      entry) const {
    if (version <= 6) {
      DxvkStateCacheEntryV6 v6;

      if (version <= 4) {
        DxvkStateCacheEntryV4 v4;

        if (!readCacheEntryTyped(stream, v4))
          return false;

        if (version == 2)
          convertEntryV2(v4);

        if (!convertEntryV4(v4, v6))
          return false;
      } else if (version <= 5) {
        DxvkStateCacheEntryV5 v5;

        if (!readCacheEntryTyped(stream, v5))
          return false;

        if (!convertEntryV5(v5, v6))
          return false;
      } else {
        if (!readCacheEntryTyped(stream, v6))
          return false;
      }

      return convertEntryV6(v6, entry);
    } else {
      return readCacheEntryTyped(stream, entry);
    }
  }

  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<char*>(&header), sizeof(header))
     || !stream.read(reinterpret_cast<char*>(&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], version);
      else
        keys[i] = g_nullShaderKey;
    }

    if (stageMask & VK_SHADER_STAGE_COMPUTE_BIT) {
      if (!data.read(entry.cpState.bsBindingMask, version))
        return false;
    } else {
      // Read packed render pass format
      uint8_t sampleCount = 0;
      uint8_t imageFormat = 0;
      uint8_t imageLayout = 0;

      if (!data.read(sampleCount, version)
       || !data.read(imageFormat, version)
       || !data.read(imageLayout, version))
        return false;

      entry.format.sampleCount = VkSampleCountFlagBits(sampleCount);
      entry.format.depth.format = VkFormat(imageFormat);
      entry.format.depth.layout = unpackImageLayout(imageLayout);

      for (uint32_t i = 0; i < MaxNumRenderTargets; i++) {
        if (!data.read(imageFormat, version)
         || !data.read(imageLayout, version))
          return false;

        entry.format.color[i].format = VkFormat(imageFormat);
        entry.format.color[i].layout = unpackImageLayout(imageLayout);
      }

      if (!validateRenderPassFormat(entry.format))
        return false;

      // Read common pipeline state
      if (!data.read(entry.gpState.bsBindingMask, version)
       || !data.read(entry.gpState.ia, version)
       || !data.read(entry.gpState.il, version)
       || !data.read(entry.gpState.rs, version)
       || !data.read(entry.gpState.ms, version)
       || !data.read(entry.gpState.ds, version)
       || !data.read(entry.gpState.om, version)
       || !data.read(entry.gpState.dsFront, version)
       || !data.read(entry.gpState.dsBack, version))
        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], version))
          return false;
      }

      // Read render target blend info
      for (uint32_t i = 0; i < MaxNumRenderTargets; i++) {
        if (!data.read(entry.gpState.omBlend[i], version))
          return false;
      }

      // Read defined vertex attributes
      for (uint32_t i = 0; i < entry.gpState.il.attributeCount(); i++) {
        if (!data.read(entry.gpState.ilAttributes[i], version))
          return false;
      }

      // Read defined vertex bindings
      for (uint32_t i = 0; i < entry.gpState.il.bindingCount(); i++) {
        if (!data.read(entry.gpState.ilBindings[i], version))
          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, version))
      return false;

    for (uint32_t i = 0; i < MaxNumSpecConstants; i++) {
      if (specConstantMask & (1 << i)) {
        if (!data.read(sc.specConstants[i], version))
          return false;
      }
    }

    return true;
  }

  void DxvkStateCache::writeCacheEntry(
          std::ostream&             stream,
          DxvkStateCacheEntry&      entry) const {
    DxvkStateCacheEntryData data;
    VkShaderStageFlags stageMask = 0;

    // Write shader hashes
    auto keys = &entry.shaders.vs;

    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(packImageLayout(entry.format.depth.layout));

      for (uint32_t i = 0; i < MaxNumRenderTargets; i++) {
        data.write(uint8_t(entry.format.color[i].format));
        data.write(packImageLayout(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<char*>(&header), sizeof(header));
    stream.write(reinterpret_cast<char*>(&hash), sizeof(hash));
    stream.write(data.data(), data.size());
    stream.flush();
  }

  bool DxvkStateCache::convertEntryV2(
          DxvkStateCacheEntryV4&    entry) const {
    // Semantics changed:
    // v2: rsDepthClampEnable
    // v3: rsDepthClipEnable
    entry.gpState.rsDepthClipEnable = !entry.gpState.rsDepthClipEnable;

    // Frontend changed: Depth bias
    // will typically be disabled
    entry.gpState.rsDepthBiasEnable = VK_FALSE;
    return true;
  }

  bool DxvkStateCache::convertEntryV4(
    const DxvkStateCacheEntryV4&    in,
          DxvkStateCacheEntryV6&    out) const {
    out.shaders = in.shaders;
    out.format  = in.format;
    out.hash    = in.hash;

    out.cpState.bsBindingMask           = in.cpState.bsBindingMask;
    out.gpState.bsBindingMask           = in.gpState.bsBindingMask;

    out.gpState.iaPrimitiveTopology     = in.gpState.iaPrimitiveTopology;
    out.gpState.iaPrimitiveRestart      = in.gpState.iaPrimitiveRestart;
    out.gpState.iaPatchVertexCount      = in.gpState.iaPatchVertexCount;

    out.gpState.ilAttributeCount        = in.gpState.ilAttributeCount;
    out.gpState.ilBindingCount          = in.gpState.ilBindingCount;

    for (uint32_t i = 0; i < in.gpState.ilAttributeCount; i++)
      out.gpState.ilAttributes[i]       = in.gpState.ilAttributes[i];

    for (uint32_t i = 0; i < in.gpState.ilBindingCount; i++) {
      out.gpState.ilBindings[i]         = in.gpState.ilBindings[i];
      out.gpState.ilDivisors[i]         = in.gpState.ilDivisors[i];
    }

    out.gpState.rsDepthClipEnable       = in.gpState.rsDepthClipEnable;
    out.gpState.rsDepthBiasEnable       = in.gpState.rsDepthBiasEnable;
    out.gpState.rsPolygonMode           = in.gpState.rsPolygonMode;
    out.gpState.rsCullMode              = in.gpState.rsCullMode;
    out.gpState.rsFrontFace             = in.gpState.rsFrontFace;
    out.gpState.rsViewportCount         = in.gpState.rsViewportCount;
    out.gpState.rsSampleCount           = in.gpState.rsSampleCount;

    out.gpState.msSampleCount           = in.gpState.msSampleCount;
    out.gpState.msSampleMask            = in.gpState.msSampleMask;
    out.gpState.msEnableAlphaToCoverage = in.gpState.msEnableAlphaToCoverage;

    out.gpState.dsEnableDepthTest       = in.gpState.dsEnableDepthTest;
    out.gpState.dsEnableDepthWrite      = in.gpState.dsEnableDepthWrite;
    out.gpState.dsEnableStencilTest     = in.gpState.dsEnableStencilTest;
    out.gpState.dsDepthCompareOp        = in.gpState.dsDepthCompareOp;
    out.gpState.dsStencilOpFront        = in.gpState.dsStencilOpFront;
    out.gpState.dsStencilOpBack         = in.gpState.dsStencilOpBack;

    out.gpState.omEnableLogicOp         = in.gpState.omEnableLogicOp;
    out.gpState.omLogicOp               = in.gpState.omLogicOp;

    for (uint32_t i = 0; i < 8; i++) {
      out.gpState.omBlendAttachments[i] = in.gpState.omBlendAttachments[i];
      out.gpState.omComponentMapping[i] = in.gpState.omComponentMapping[i];
    }

    return true;
  }

  bool DxvkStateCache::convertEntryV5(
    const DxvkStateCacheEntryV5&    in,
          DxvkStateCacheEntryV6&    out) const {
    out.shaders = in.shaders;
    out.gpState = in.gpState;
    out.format  = in.format;
    out.hash    = in.hash;

    out.cpState.bsBindingMask = in.cpState.bsBindingMask;
    return true;
  }

  bool DxvkStateCache::convertEntryV6(
    const DxvkStateCacheEntryV6&    in,
          DxvkStateCacheEntry&      out) const {
    out.shaders = in.shaders;
    out.format  = in.format;
    out.hash    = in.hash;

    if (in.shaders.cs.eq(g_nullShaderKey)) {
      // Binding mask
      out.gpState.bsBindingMask = in.gpState.bsBindingMask.convert();

      // Graphics state
      out.gpState.ia = DxvkIaInfo(
        in.gpState.iaPrimitiveTopology,
        in.gpState.iaPrimitiveRestart,
        in.gpState.iaPatchVertexCount);

      out.gpState.il = DxvkIlInfo(
        in.gpState.ilAttributeCount,
        in.gpState.ilBindingCount);

      for (uint32_t i = 0; i < in.gpState.ilAttributeCount; i++) {
        out.gpState.ilAttributes[i] = DxvkIlAttribute(
          in.gpState.ilAttributes[i].location,
          in.gpState.ilAttributes[i].binding,
          in.gpState.ilAttributes[i].format,
          in.gpState.ilAttributes[i].offset);
      }

      for (uint32_t i = 0; i < in.gpState.ilBindingCount; i++) {
        out.gpState.ilBindings[i] = DxvkIlBinding(
          in.gpState.ilBindings[i].binding,
          in.gpState.ilBindings[i].stride,
          in.gpState.ilBindings[i].inputRate,
          in.gpState.ilDivisors[i]);
      }

      out.gpState.rs = DxvkRsInfo(
        in.gpState.rsDepthClipEnable,
        in.gpState.rsDepthBiasEnable,
        in.gpState.rsPolygonMode,
        in.gpState.rsCullMode,
        in.gpState.rsFrontFace,
        in.gpState.rsViewportCount,
        in.gpState.rsSampleCount,
        VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT);

      out.gpState.ms = DxvkMsInfo(
        in.gpState.msSampleCount,
        in.gpState.msSampleMask,
        in.gpState.msEnableAlphaToCoverage);

      out.gpState.ds = DxvkDsInfo(
        in.gpState.dsEnableDepthTest,
        in.gpState.dsEnableDepthWrite,
        in.gpState.dsEnableDepthBoundsTest,
        in.gpState.dsEnableStencilTest,
        in.gpState.dsDepthCompareOp);

      out.gpState.dsFront = DxvkDsStencilOp(in.gpState.dsStencilOpFront);
      out.gpState.dsBack  = DxvkDsStencilOp(in.gpState.dsStencilOpBack);

      out.gpState.om = DxvkOmInfo(
        in.gpState.omEnableLogicOp,
        in.gpState.omLogicOp);

      for (uint32_t i = 0; i < 8 && i < MaxNumRenderTargets; i++) {
        out.gpState.omBlend[i] = DxvkOmAttachmentBlend(
          in.gpState.omBlendAttachments[i].blendEnable,
          in.gpState.omBlendAttachments[i].srcColorBlendFactor,
          in.gpState.omBlendAttachments[i].dstColorBlendFactor,
          in.gpState.omBlendAttachments[i].colorBlendOp,
          in.gpState.omBlendAttachments[i].srcAlphaBlendFactor,
          in.gpState.omBlendAttachments[i].dstAlphaBlendFactor,
          in.gpState.omBlendAttachments[i].alphaBlendOp,
          in.gpState.omBlendAttachments[i].colorWriteMask);

        out.gpState.omSwizzle[i] = DxvkOmAttachmentSwizzle(
          in.gpState.omComponentMapping[i]);
      }

      // Specialization constants
      for (uint32_t i = 0; i < 8 && i < MaxNumSpecConstants; i++)
        out.cpState.sc.specConstants[i] = in.cpState.scSpecConstants[i];
    } else {
      // Binding mask
      out.cpState.bsBindingMask = in.cpState.bsBindingMask.convert();

      for (uint32_t i = 0; i < 8 && i < MaxNumSpecConstants; i++)
        out.gpState.sc.specConstants[i] = in.gpState.scSpecConstants[i];
    }

    return true;
  }

  void DxvkStateCache::workerFunc() {
    env::setThreadName("dxvk-shader");

    while (!m_stopThreads.load()) {
      WorkerItem item;

      { std::unique_lock<dxvk::mutex> lock(m_workerLock);

        if (m_workerQueue.empty()) {
          m_workerBusy -= 1;
          m_workerCond.wait(lock, [this] () {
            return m_workerQueue.size()
                || m_stopThreads.load();
          });

          if (!m_workerQueue.empty())
            m_workerBusy += 1;
        }

        if (m_workerQueue.empty())
          break;

        item = m_workerQueue.front();
        m_workerQueue.pop();
      }

      compilePipelines(item);
    }
  }

  void DxvkStateCache::writerFunc() {
    env::setThreadName("dxvk-writer");

    std::ofstream file;

    while (!m_stopThreads.load()) {
      DxvkStateCacheEntry entry;

      { std::unique_lock<dxvk::mutex> lock(m_writerLock);

        m_writerCond.wait(lock, [this] () {
          return m_writerQueue.size()
              || m_stopThreads.load();
        });

        if (m_writerQueue.size() == 0)
          break;

        entry = m_writerQueue.front();
        m_writerQueue.pop();
      }

      if (!file.is_open()) {
        file.open(getCacheFileName().c_str(),
          std::ios_base::binary |
          std::ios_base::app);
      }

      writeCacheEntry(file, entry);
    }
  }

  void DxvkStateCache::createWorkers() {
    if (m_workerThreads.empty()) {
      // Use half the available CPU cores for pipeline compilation
      uint32_t numCpuCores = dxvk::thread::hardware_concurrency();
      uint32_t numWorkers  = ((std::max(1u, numCpuCores) - 1) * 5) / 7;

      if (numWorkers <  1) numWorkers =  1;
      if (numWorkers > 32) numWorkers = 32;

      if (m_device->config().numCompilerThreads > 0)
        numWorkers = m_device->config().numCompilerThreads;

      Logger::info(str::format("DXVK: Using ", numWorkers, " compiler threads"));

      // Start the worker threads and the file writer
      m_workerBusy.store(numWorkers);

      for (uint32_t i = 0; i < numWorkers; i++) {
        m_workerThreads.emplace_back([this] () { workerFunc(); });
        m_workerThreads[i].set_priority(ThreadPriority::Lowest);
      }
    }
  }

  void DxvkStateCache::createWriter() {
    if (!m_writerThread.joinable())
      m_writerThread = dxvk::thread([this] () { writerFunc(); });
  }

  std::wstring DxvkStateCache::getCacheFileName() const {
    std::string path = getCacheDir();

    if (!path.empty() && *path.rbegin() != '/')
      path += '/';

    std::string exeName = env::getExeBaseName();
    path += exeName + ".dxvk-cache";
    return str::tows(path.c_str());
  }

  std::string DxvkStateCache::getCacheDir() const {
    return env::getEnvVar("DXVK_STATE_CACHE_PATH");
  }

  uint8_t DxvkStateCache::packImageLayout(
          VkImageLayout             layout) {
    switch (layout) {
      case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: return 0x80;
      case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: return 0x81;
      default: return uint8_t(layout);
    }
  }

  VkImageLayout DxvkStateCache::unpackImageLayout(
          uint8_t                   layout) {
    switch (layout) {
      case 0x80: return VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL;
      case 0x81: return VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL;
      default: return VkImageLayout(layout);
    }
  }

  bool DxvkStateCache::validateRenderPassFormat(
    const DxvkRenderPassFormat&     format) {
    bool valid = true;

    if (format.depth.format) {
      valid &= format.depth.layout == VK_IMAGE_LAYOUT_GENERAL
            || format.depth.layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
            || format.depth.layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
            || format.depth.layout == VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL
            || format.depth.layout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL;
    }

    for (uint32_t i = 0; i < MaxNumRenderTargets && valid; i++) {
      if (format.color[i].format) {
        valid &= format.color[i].layout == VK_IMAGE_LAYOUT_GENERAL
              || format.color[i].layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
      }
    }

    return valid;
  }

}


code:
#pragma once

#include <atomic>
#include <condition_variable>
#include <fstream>
#include <mutex>
#include <queue>
#include <unordered_map>
#include <vector>

#include "dxvk_state_cache_types.h"

namespace dxvk {

  class DxvkDevice;

  /**
   * \brief State cache
   *
   * The shader state cache stores state vectors and
   * render pass formats of all pipelines used in a
   * game, which allows DXVK to compile them ahead
   * of time instead of compiling them on the first
   * draw.
   */
  class DxvkStateCache : public RcObject {

  public:

    DxvkStateCache(
            DxvkDevice*           device,
            DxvkPipelineManager*  pipeManager,
            DxvkRenderPassPool*   passManager);

    ~DxvkStateCache();

    /**
     * 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,
      const DxvkRenderPassFormat&           format);

    /**
     * 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
     *
     * Makes the shader available to the pipeline
     * compiler, and starts compiling all pipelines
     * for which all shaders become available.
     * \param [in] shader The shader to add
     */
    void registerShader(
      const Rc<DxvkShader>&                 shader);

    /**
     * \brief Explicitly stops worker threads
     */
    void stopWorkerThreads();

    /**
     * \brief Checks whether compiler threads are busy
     * \returns \c true if we're compiling shaders
     */
    bool isCompilingShaders() {
      return m_workerBusy.load() > 0;
    }

  private:

    using WriterItem = DxvkStateCacheEntry;

    struct WorkerItem {
      DxvkGraphicsPipelineShaders gp;
      DxvkComputePipelineShaders  cp;
    };

    DxvkDevice*                       m_device;
    DxvkPipelineManager*              m_pipeManager;
    DxvkRenderPassPool*               m_passManager;

    std::vector<DxvkStateCacheEntry>  m_entries;
    std::atomic<bool>                 m_stopThreads = { false };

    dxvk::mutex                       m_entryLock;

    std::unordered_multimap<
      DxvkStateCacheKey, size_t,
      DxvkHash, DxvkEq> m_entryMap;

    std::unordered_multimap<
      DxvkShaderKey, DxvkStateCacheKey,
      DxvkHash, DxvkEq> m_pipelineMap;

    std::unordered_map<
      DxvkShaderKey, Rc<DxvkShader>,
      DxvkHash, DxvkEq> m_shaderMap;

    dxvk::mutex                       m_workerLock;
    dxvk::condition_variable          m_workerCond;
    std::queue<WorkerItem>            m_workerQueue;
    std::atomic<uint32_t>             m_workerBusy;
    std::vector<dxvk::thread>         m_workerThreads;

    dxvk::mutex                       m_writerLock;
    dxvk::condition_variable          m_writerCond;
    std::queue<WriterItem>            m_writerQueue;
    dxvk::thread                      m_writerThread;

    DxvkShaderKey getShaderKey(
      const Rc<DxvkShader>&           shader) const;

    bool getShaderByKey(
      const DxvkShaderKey&            key,
            Rc<DxvkShader>&           shader) const;

    void mapPipelineToEntry(
      const DxvkStateCacheKey&        key,
            size_t                    entryId);

    void mapShaderToPipeline(
      const DxvkShaderKey&            shader,
      const DxvkStateCacheKey&        key);

    void compilePipelines(
      const WorkerItem&               item);

    bool readCacheFile();

    bool readCacheHeader(
            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,
            DxvkStateCacheEntry&      entry) const;

    void writeCacheEntry(
            std::ostream&             stream,
            DxvkStateCacheEntry&      entry) const;

    bool convertEntryV2(
            DxvkStateCacheEntryV4&    entry) const;

    bool convertEntryV4(
      const DxvkStateCacheEntryV4&    in,
            DxvkStateCacheEntryV6&    out) const;

    bool convertEntryV5(
      const DxvkStateCacheEntryV5&    in,
            DxvkStateCacheEntryV6&    out) const;

    bool convertEntryV6(
      const DxvkStateCacheEntryV6&    in,
            DxvkStateCacheEntry&      out) const;

    void workerFunc();

    void writerFunc();

    void createWorkers();

    void createWriter();

    std::wstring getCacheFileName() const;

    std::string getCacheDir() const;

    static uint8_t packImageLayout(
            VkImageLayout             layout);

    static VkImageLayout unpackImageLayout(
            uint8_t                   layout);

    static bool validateRenderPassFormat(
      const DxvkRenderPassFormat&     format);

  };

}


code:
#pragma once

#include "dxvk_pipemanager.h"
#include "dxvk_renderpass.h"

namespace dxvk {

  /**
   * \brief State cache entry key
   *
   * Stores the shader keys for all
   * graphics shader stages. Used to
   * look up cached state entries.
   */
  struct DxvkStateCacheKey {
    DxvkShaderKey vs;
    DxvkShaderKey tcs;
    DxvkShaderKey tes;
    DxvkShaderKey gs;
    DxvkShaderKey fs;
    DxvkShaderKey cs;

    bool eq(const DxvkStateCacheKey& key) const;

    size_t hash() const;
  };

  /**
   * \brief State entry
   *
   * Stores the shaders used in a pipeline, as well
   * as the full state vector, including its render
   * pass format. This also includes a SHA-1 hash
   * that is used as a check sum to verify integrity.
   */
  struct DxvkStateCacheEntry {
    DxvkStateCacheKey             shaders;
    DxvkGraphicsPipelineStateInfo gpState;
    DxvkComputePipelineStateInfo  cpState;
    DxvkRenderPassFormat          format;
    Sha1Hash                      hash;
  };

  /**
   * \brief State cache header
   *
   * Stores the state cache format version. If an
   * existing cache file is incompatible to the
   * current version, it will be discarded.
   */
  struct DxvkStateCacheHeader {
    char     magic[4]   = { 'D', 'X', 'V', 'K' };
    uint32_t version    = 10;
    uint32_t entrySize  = 0; /* no longer meaningful */
  };

  static_assert(sizeof(DxvkStateCacheHeader) == 12);

  class DxvkBindingMaskV8 : DxvkBindingSet<128> {

  public:

    DxvkBindingMask convert() const {
      DxvkBindingMask result = { };
      for (uint32_t i = 0; i < 128; i++)
        result.set(i, test(i));
      return result;
    }

  };

  class DxvkIlBindingV9 {

  public:

    uint32_t m_binding                : 5;
    uint32_t m_stride                 : 12;
    uint32_t m_inputRate              : 1;
    uint32_t m_reserved               : 14;
    uint32_t m_divisor;

    DxvkIlBinding convert() const {
      return DxvkIlBinding(m_binding, m_stride,
        VkVertexInputRate(m_inputRate), m_divisor);
    }

  };

  /**
   * \brief Version 4 graphics pipeline state
   */
  struct DxvkGraphicsPipelineStateInfoV4 {
    DxvkBindingMaskV8                   bsBindingMask;

    VkPrimitiveTopology                 iaPrimitiveTopology;
    VkBool32                            iaPrimitiveRestart;
    uint32_t                            iaPatchVertexCount;

    uint32_t                            ilAttributeCount;
    uint32_t                            ilBindingCount;
    VkVertexInputAttributeDescription   ilAttributes[32];
    VkVertexInputBindingDescription     ilBindings[32];
    uint32_t                            ilDivisors[32];

    VkBool32                            rsDepthClipEnable;
    VkBool32                            rsDepthBiasEnable;
    VkPolygonMode                       rsPolygonMode;
    VkCullModeFlags                     rsCullMode;
    VkFrontFace                         rsFrontFace;
    uint32_t                            rsViewportCount;
    VkSampleCountFlags                  rsSampleCount;

    VkSampleCountFlags                  msSampleCount;
    uint32_t                            msSampleMask;
    VkBool32                            msEnableAlphaToCoverage;

    VkCompareOp                         xsAlphaCompareOp;

    VkBool32                            dsEnableDepthTest;
    VkBool32                            dsEnableDepthWrite;
    VkBool32                            dsEnableStencilTest;
    VkCompareOp                         dsDepthCompareOp;
    VkStencilOpState                    dsStencilOpFront;
    VkStencilOpState                    dsStencilOpBack;

    VkBool32                            omEnableLogicOp;
    VkLogicOp                           omLogicOp;
    VkPipelineColorBlendAttachmentState omBlendAttachments[8];
    VkComponentMapping                  omComponentMapping[8];
  };

  /**
   * \brief Version 6 graphics pipeline state
   */
  struct DxvkGraphicsPipelineStateInfoV6 {
    DxvkBindingMaskV8                   bsBindingMask;

    VkPrimitiveTopology                 iaPrimitiveTopology;
    VkBool32                            iaPrimitiveRestart;
    uint32_t                            iaPatchVertexCount;

    uint32_t                            ilAttributeCount;
    uint32_t                            ilBindingCount;
    VkVertexInputAttributeDescription   ilAttributes[32];
    VkVertexInputBindingDescription     ilBindings[32];
    uint32_t                            ilDivisors[32];

    VkBool32                            rsDepthClipEnable;
    VkBool32                            rsDepthBiasEnable;
    VkPolygonMode                       rsPolygonMode;
    VkCullModeFlags                     rsCullMode;
    VkFrontFace                         rsFrontFace;
    uint32_t                            rsViewportCount;
    VkSampleCountFlags                  rsSampleCount;

    VkSampleCountFlags                  msSampleCount;
    uint32_t                            msSampleMask;
    VkBool32                            msEnableAlphaToCoverage;

    VkBool32                            dsEnableDepthTest;
    VkBool32                            dsEnableDepthWrite;
    VkBool32                            dsEnableDepthBoundsTest;
    VkBool32                            dsEnableStencilTest;
    VkCompareOp                         dsDepthCompareOp;
    VkStencilOpState                    dsStencilOpFront;
    VkStencilOpState                    dsStencilOpBack;

    VkBool32                            omEnableLogicOp;
    VkLogicOp                           omLogicOp;
    VkPipelineColorBlendAttachmentState omBlendAttachments[8];
    VkComponentMapping                  omComponentMapping[8];

    uint32_t                            scSpecConstants[8];
  };

  /**
   * \brief Version 5 compute pipeline state
   */
  struct DxvkComputePipelineStateInfoV5 {
    DxvkBindingMaskV8                   bsBindingMask;
  };

  /**
   * \brief Version 6 compute pipeline state
   */
  struct DxvkComputePipelineStateInfoV6 {
    DxvkBindingMaskV8                   bsBindingMask;
    uint32_t                            scSpecConstants[8];
  };

  /**
   * \brief Version 4 state cache entry
   */
  struct DxvkStateCacheEntryV4 {
    DxvkStateCacheKey               shaders;
    DxvkGraphicsPipelineStateInfoV4 gpState;
    DxvkComputePipelineStateInfoV5  cpState;
    DxvkRenderPassFormat            format;
    Sha1Hash                        hash;
  };

  /**
   * \brief Version 5 state cache entry
   */
  struct DxvkStateCacheEntryV5 {
    DxvkStateCacheKey               shaders;
    DxvkGraphicsPipelineStateInfoV6 gpState;
    DxvkComputePipelineStateInfoV5  cpState;
    DxvkRenderPassFormat            format;
    Sha1Hash                        hash;
  };

  /**
   * \brief Version 6 state cache entry
   */
  struct DxvkStateCacheEntryV6 {
    DxvkStateCacheKey               shaders;
    DxvkGraphicsPipelineStateInfoV6 gpState;
    DxvkComputePipelineStateInfoV6  cpState;
    DxvkRenderPassFormat            format;
    Sha1Hash                        hash;
  };

}


code:
#include "dxvk_stats.h"

namespace dxvk {

  DxvkStatCounters::DxvkStatCounters() {
    this->reset();
  }

  DxvkStatCounters::~DxvkStatCounters() {

  }

  DxvkStatCounters DxvkStatCounters::diff(const DxvkStatCounters& other) const {
    DxvkStatCounters result;
    for (size_t i = 0; i < m_counters.size(); i++)
      result.m_counters[i] = m_counters[i] - other.m_counters[i];
    return result;
  }

  void DxvkStatCounters::merge(const DxvkStatCounters& other) {
    for (size_t i = 0; i < m_counters.size(); i++)
      m_counters[i] += other.m_counters[i];
  }

  void DxvkStatCounters::reset() {
    for (size_t i = 0; i < m_counters.size(); i++)
      m_counters[i] = 0;
  }

}


code:
#pragma once

#include "dxvk_include.h"

namespace dxvk {

  /**
   * \brief Named stat counters
   *
   * Enumerates available stat counters. Used
   * thogether with \ref DxvkStatCounters.
   */
  enum class DxvkStatCounter : uint32_t {
    CmdDrawCalls,             ///< Number of draw calls
    CmdDispatchCalls,         ///< Number of compute calls
    CmdRenderPassCount,       ///< Number of render passes
    CmdBarrierCount,          ///< Number of pipeline barriers
    PipeCountGraphics,        ///< Number of graphics pipelines
    PipeCountCompute,         ///< Number of compute pipelines
    PipeCompilerBusy,         ///< Boolean indicating compiler activity
    QueueSubmitCount,         ///< Number of command buffer submissions
    QueuePresentCount,        ///< Number of present calls / frames
    GpuSyncCount,             ///< Number of GPU synchronizations
    GpuSyncTicks,             ///< Time spent waiting for GPU
    GpuIdleTicks,             ///< GPU idle time in microseconds
    CsSyncCount,              ///< CS thread synchronizations
    CsSyncTicks,              ///< Time spent waiting on CS
    CsChunkCount,             ///< Submitted CS chunks
    NumCounters,              ///< Number of counters available
  };

  /**
   * \brief Stat counters
   *
   * Collects various statistics that may be
   * useful to identify performance bottlenecks.
   */
  class DxvkStatCounters {

  public:

    DxvkStatCounters();
    ~DxvkStatCounters();

    /**
     * \brief Retrieves a counter value
     *
     * \param [in] ctr The counter
     * \returns Counter value
     */
    uint64_t getCtr(DxvkStatCounter ctr) const {
      return m_counters[uint32_t(ctr)];
    }

    /**
     * \brief Sets a counter value
     *
     * \param [in] ctr The counter
     * \param [in] val Counter value
     */
    void setCtr(DxvkStatCounter ctr, uint64_t val) {
      m_counters[uint32_t(ctr)] = val;
    }

    /**
     * \brief Increments a counter value
     *
     * \param [in] ctr Counter to increment
     * \param [in] val Number to add to counter value
     */
    void addCtr(DxvkStatCounter ctr, uint64_t val) {
      m_counters[uint32_t(ctr)] += val;
    }

    /**
     * \brief Resets a counter
     * \param [in] ctr The counter
     */
    void clrCtr(DxvkStatCounter ctr) {
      m_counters[uint32_t(ctr)] = 0;
    }

    /**
     * \brief Computes difference
     *
     * Computes difference between counter values.
     * \param [in] other Counters to subtract
     * \returns Difference between counter sets
     */
    DxvkStatCounters diff(const DxvkStatCounters& other) const;

    /**
     * \brief Merges counters
     *
     * Adds counter values from another set
     * of counters to this set of counters.
     * \param [in] other Counters to add
     */
    void merge(const DxvkStatCounters& other);

    /**
     * \brief Resets counters
     *
     * Sets all counters to zero.
     */
    void reset();

  private:

    std::array<uint64_t, uint32_t(DxvkStatCounter::NumCounters)> m_counters;

  };

}


code:
#include "dxvk_swapchain_blitter.h"

#include <dxvk_present_frag.h>
#include <dxvk_present_frag_blit.h>
#include <dxvk_present_frag_ms.h>
#include <dxvk_present_frag_ms_amd.h>
#include <dxvk_present_vert.h>

namespace dxvk {

  DxvkSwapchainBlitter::DxvkSwapchainBlitter(const Rc<DxvkDevice>& device)
  : m_device(device) {
    this->createSampler();
    this->createShaders();
  }

  DxvkSwapchainBlitter::~DxvkSwapchainBlitter() {

  }

  void DxvkSwapchainBlitter::presentImage(
          DxvkContext*        ctx,
    const Rc<DxvkImageView>&  dstView,
          VkRect2D            dstRect,
    const Rc<DxvkImageView>&  srcView,
          VkRect2D            srcRect) {
    if (m_gammaDirty)
      this->updateGammaTexture(ctx);

    // Fix up default present areas if necessary
    if (!dstRect.extent.width || !dstRect.extent.height) {
      dstRect.offset = { 0, 0 };
      dstRect.extent = {
        dstView->imageInfo().extent.width,
        dstView->imageInfo().extent.height };
    }

    if (!srcRect.extent.width || !srcRect.extent.height) {
      srcRect.offset = { 0, 0 };
      srcRect.extent = {
        srcView->imageInfo().extent.width,
        srcView->imageInfo().extent.height };
    }

    bool sameSize = dstRect.extent == srcRect.extent;
    bool usedResolveImage = false;

    if (srcView->imageInfo().sampleCount == VK_SAMPLE_COUNT_1_BIT) {
      this->draw(ctx, sameSize ? m_fsCopy : m_fsBlit,
        dstView, dstRect, srcView, srcRect);
    } else if (sameSize) {
      this->draw(ctx, m_fsResolve,
        dstView, dstRect, srcView, srcRect);
    } else {
        if (m_resolveImage == nullptr
         || m_resolveImage->info().extent != srcView->imageInfo().extent
         || m_resolveImage->info().format != srcView->imageInfo().format)
          this->createResolveImage(srcView->imageInfo());

      this->resolve(ctx, m_resolveView, srcView);
      this->draw(ctx, m_fsBlit, dstView, dstRect, m_resolveView, srcRect);

      usedResolveImage = true;
    }

    if (!usedResolveImage)
      this->destroyResolveImage();
  }

  void DxvkSwapchainBlitter::setGammaRamp(
          uint32_t            cpCount,
    const DxvkGammaCp*        cpData) {
    VkDeviceSize size = cpCount * sizeof(*cpData);

    if (cpCount) {
      if (m_gammaBuffer == nullptr || m_gammaBuffer->info().size < size) {
        DxvkBufferCreateInfo bufInfo;
        bufInfo.size = size;
        bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
        bufInfo.stages = VK_PIPELINE_STAGE_TRANSFER_BIT;
        bufInfo.access = VK_ACCESS_TRANSFER_READ_BIT;

        m_gammaBuffer = m_device->createBuffer(bufInfo,
          VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
          VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
      }

      if (!m_gammaSlice.handle)
        m_gammaSlice = m_gammaBuffer->allocSlice();

      std::memcpy(m_gammaSlice.mapPtr, cpData, size);
    } else {
      m_gammaBuffer = nullptr;
      m_gammaSlice = DxvkBufferSliceHandle();
    }

    m_gammaCpCount = cpCount;
    m_gammaDirty = true;
  }

  void DxvkSwapchainBlitter::draw(
          DxvkContext*        ctx,
    const Rc<DxvkShader>&     fs,
    const Rc<DxvkImageView>&  dstView,
          VkRect2D            dstRect,
    const Rc<DxvkImageView>&  srcView,
          VkRect2D            srcRect) {
    DxvkInputAssemblyState iaState;
    iaState.primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
    iaState.primitiveRestart  = VK_FALSE;
    iaState.patchVertexCount  = 0;
    ctx->setInputAssemblyState(iaState);
    ctx->setInputLayout(0, nullptr, 0, nullptr);

    DxvkRasterizerState rsState;
    rsState.polygonMode        = VK_POLYGON_MODE_FILL;
    rsState.cullMode           = VK_CULL_MODE_BACK_BIT;
    rsState.frontFace          = VK_FRONT_FACE_COUNTER_CLOCKWISE;
    rsState.depthClipEnable    = VK_FALSE;
    rsState.depthBiasEnable    = VK_FALSE;
    rsState.conservativeMode   = VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT;
    rsState.sampleCount        = VK_SAMPLE_COUNT_1_BIT;
    ctx->setRasterizerState(rsState);

    DxvkMultisampleState msState;
    msState.sampleMask            = 0xffffffff;
    msState.enableAlphaToCoverage = VK_FALSE;
    ctx->setMultisampleState(msState);

    VkStencilOpState stencilOp;
    stencilOp.failOp      = VK_STENCIL_OP_KEEP;
    stencilOp.passOp      = VK_STENCIL_OP_KEEP;
    stencilOp.depthFailOp = VK_STENCIL_OP_KEEP;
    stencilOp.compareOp   = VK_COMPARE_OP_ALWAYS;
    stencilOp.compareMask = 0xFFFFFFFF;
    stencilOp.writeMask   = 0xFFFFFFFF;
    stencilOp.reference   = 0;

    DxvkDepthStencilState dsState;
    dsState.enableDepthTest   = VK_FALSE;
    dsState.enableDepthWrite  = VK_FALSE;
    dsState.enableStencilTest = VK_FALSE;
    dsState.depthCompareOp    = VK_COMPARE_OP_ALWAYS;
    dsState.stencilOpFront    = stencilOp;
    dsState.stencilOpBack     = stencilOp;
    ctx->setDepthStencilState(dsState);

    DxvkLogicOpState loState;
    loState.enableLogicOp = VK_FALSE;
    loState.logicOp       = VK_LOGIC_OP_NO_OP;
    ctx->setLogicOpState(loState);

    DxvkBlendMode blendMode;
    blendMode.enableBlending  = VK_FALSE;
    blendMode.colorSrcFactor  = VK_BLEND_FACTOR_ONE;
    blendMode.colorDstFactor  = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
    blendMode.colorBlendOp    = VK_BLEND_OP_ADD;
    blendMode.alphaSrcFactor  = VK_BLEND_FACTOR_ONE;
    blendMode.alphaDstFactor  = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
    blendMode.alphaBlendOp    = VK_BLEND_OP_ADD;
    blendMode.writeMask       = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
                              | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
    ctx->setBlendMode(0, blendMode);

    VkViewport viewport;
    viewport.x        = float(dstRect.offset.x);
    viewport.y        = float(dstRect.offset.y);
    viewport.width    = float(dstRect.extent.width);
    viewport.height   = float(dstRect.extent.height);
    viewport.minDepth = 0.0f;
    viewport.maxDepth = 1.0f;

    ctx->setViewports(1, &viewport, &dstRect);

    DxvkRenderTargets renderTargets;
    renderTargets.color[0].view   = dstView;
    renderTargets.color[0].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

    ctx->bindRenderTargets(renderTargets);

    VkExtent2D dstExtent = {
      dstView->imageInfo().extent.width,
      dstView->imageInfo().extent.height };

    if (dstRect.extent == dstExtent)
      ctx->discardImageView(dstView, VK_IMAGE_ASPECT_COLOR_BIT);
    else
      ctx->clearRenderTarget(dstView, VK_IMAGE_ASPECT_COLOR_BIT, VkClearValue());

    ctx->bindResourceSampler(BindingIds::Image, m_samplerPresent);
    ctx->bindResourceSampler(BindingIds::Gamma, m_samplerGamma);

    ctx->bindResourceView(BindingIds::Image, srcView, nullptr);
    ctx->bindResourceView(BindingIds::Gamma, m_gammaView, nullptr);

    ctx->bindShader(VK_SHADER_STAGE_VERTEX_BIT, m_vs);
    ctx->bindShader(VK_SHADER_STAGE_FRAGMENT_BIT, fs);

    PresenterArgs args;
    args.srcOffset = srcRect.offset;

    if (dstRect.extent == srcRect.extent)
      args.dstOffset = dstRect.offset;
    else
      args.srcExtent = srcRect.extent;

    ctx->pushConstants(0, sizeof(args), &args);

    ctx->setSpecConstant(VK_PIPELINE_BIND_POINT_GRAPHICS, 0, srcView->imageInfo().sampleCount);
    ctx->draw(3, 1, 0, 0);
    ctx->setSpecConstant(VK_PIPELINE_BIND_POINT_GRAPHICS, 0, 0);
  }

  void DxvkSwapchainBlitter::resolve(
          DxvkContext*        ctx,
    const Rc<DxvkImageView>&  dstView,
    const Rc<DxvkImageView>&  srcView) {
    VkImageResolve resolve;
    resolve.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
    resolve.srcOffset      = { 0, 0, 0 };
    resolve.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
    resolve.dstOffset      = { 0, 0, 0 };
    resolve.extent         = dstView->imageInfo().extent;
    ctx->resolveImage(dstView->image(), srcView->image(), resolve, VK_FORMAT_UNDEFINED);
  }

  void DxvkSwapchainBlitter::updateGammaTexture(DxvkContext* ctx) {
    uint32_t n = m_gammaCpCount;

    if (n) {
      // Reuse existing image if possible
      if (m_gammaImage == nullptr || m_gammaImage->info().extent.width != n) {
        DxvkImageCreateInfo imgInfo;
        imgInfo.type        = VK_IMAGE_TYPE_1D;
        imgInfo.format      = VK_FORMAT_R16G16B16A16_UNORM;
        imgInfo.flags       = 0;
        imgInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
        imgInfo.extent      = { n, 1, 1 };
        imgInfo.numLayers   = 1;
        imgInfo.mipLevels   = 1;
        imgInfo.usage       = VK_IMAGE_USAGE_TRANSFER_DST_BIT
                            | VK_IMAGE_USAGE_SAMPLED_BIT;
        imgInfo.stages      = VK_PIPELINE_STAGE_TRANSFER_BIT
                            | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
        imgInfo.access      = VK_ACCESS_TRANSFER_WRITE_BIT
                            | VK_ACCESS_SHADER_READ_BIT;
        imgInfo.tiling      = VK_IMAGE_TILING_OPTIMAL;
        imgInfo.layout      = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;

        m_gammaImage = m_device->createImage(
          imgInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

        DxvkImageViewCreateInfo viewInfo;
        viewInfo.type       = VK_IMAGE_VIEW_TYPE_1D;
        viewInfo.format     = VK_FORMAT_R16G16B16A16_UNORM;
        viewInfo.usage      = VK_IMAGE_USAGE_SAMPLED_BIT;
        viewInfo.aspect     = VK_IMAGE_ASPECT_COLOR_BIT;
        viewInfo.minLevel   = 0;
        viewInfo.numLevels  = 1;
        viewInfo.minLayer   = 0;
        viewInfo.numLayers  = 1;

        m_gammaView = m_device->createImageView(m_gammaImage, viewInfo);
      }

      ctx->invalidateBuffer(m_gammaBuffer, m_gammaSlice);
      ctx->copyBufferToImage(m_gammaImage,
        VkImageSubresourceLayers { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 },
        VkOffset3D { 0, 0, 0 },
        VkExtent3D { n, 1, 1 },
        m_gammaBuffer, 0, 0, 0);

      m_gammaSlice = DxvkBufferSliceHandle();
    } else {
      m_gammaImage = nullptr;
      m_gammaView  = nullptr;
    }

    m_gammaDirty = false;
  }

  void DxvkSwapchainBlitter::createSampler() {
    DxvkSamplerCreateInfo samplerInfo;
    samplerInfo.magFilter       = VK_FILTER_LINEAR;
    samplerInfo.minFilter       = VK_FILTER_LINEAR;
    samplerInfo.mipmapMode      = VK_SAMPLER_MIPMAP_MODE_NEAREST;
    samplerInfo.mipmapLodBias   = 0.0f;
    samplerInfo.mipmapLodMin    = 0.0f;
    samplerInfo.mipmapLodMax    = 0.0f;
    samplerInfo.useAnisotropy   = VK_FALSE;
    samplerInfo.maxAnisotropy   = 1.0f;
    samplerInfo.addressModeU    = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
    samplerInfo.addressModeV    = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
    samplerInfo.addressModeW    = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
    samplerInfo.compareToDepth  = VK_FALSE;
    samplerInfo.compareOp       = VK_COMPARE_OP_ALWAYS;
    samplerInfo.borderColor     = VkClearColorValue();
    samplerInfo.usePixelCoord   = VK_TRUE;
    samplerInfo.nonSeamless     = VK_FALSE;
    m_samplerPresent = m_device->createSampler(samplerInfo);

    samplerInfo.addressModeU    = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    samplerInfo.addressModeV    = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    samplerInfo.addressModeW    = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    samplerInfo.usePixelCoord   = VK_FALSE;
    m_samplerGamma = m_device->createSampler(samplerInfo);
  }

  void DxvkSwapchainBlitter::createShaders() {
    SpirvCodeBuffer vsCode(dxvk_present_vert);
    SpirvCodeBuffer fsCodeBlit(dxvk_present_frag_blit);
    SpirvCodeBuffer fsCodeCopy(dxvk_present_frag);
    SpirvCodeBuffer fsCodeResolve(dxvk_present_frag_ms);
    SpirvCodeBuffer fsCodeResolveAmd(dxvk_present_frag_ms_amd);

    const std::array<DxvkResourceSlot, 2> fsResourceSlots = {{
      { BindingIds::Image, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_IMAGE_VIEW_TYPE_2D },
      { BindingIds::Gamma, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_IMAGE_VIEW_TYPE_1D },
    }};

    DxvkShaderCreateInfo vsInfo;
    vsInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
    vsInfo.outputMask = 0x1;
    m_vs = new DxvkShader(vsInfo, std::move(vsCode));

    DxvkShaderCreateInfo fsInfo;
    fsInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
    fsInfo.resourceSlotCount = fsResourceSlots.size();
    fsInfo.resourceSlots = fsResourceSlots.data();
    fsInfo.pushConstSize = sizeof(PresenterArgs);
    fsInfo.inputMask = 0x1;
    fsInfo.outputMask = 0x1;
    m_fsBlit = new DxvkShader(fsInfo, std::move(fsCodeBlit));

    fsInfo.inputMask = 0;
    m_fsCopy = new DxvkShader(fsInfo, std::move(fsCodeCopy));
    m_fsResolve = new DxvkShader(fsInfo, m_device->extensions().amdShaderFragmentMask
      ? std::move(fsCodeResolveAmd)
      : std::move(fsCodeResolve));
  }

  void DxvkSwapchainBlitter::createResolveImage(const DxvkImageCreateInfo& info) {
    DxvkImageCreateInfo newInfo;
    newInfo.type = VK_IMAGE_TYPE_2D;
    newInfo.format = info.format;
    newInfo.flags = 0;
    newInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
    newInfo.extent = info.extent;
    newInfo.numLayers = 1;
    newInfo.mipLevels = 1;
    newInfo.usage  = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
                   | VK_IMAGE_USAGE_TRANSFER_DST_BIT
                   | VK_IMAGE_USAGE_SAMPLED_BIT;
    newInfo.stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
                   | VK_PIPELINE_STAGE_TRANSFER_BIT
                   | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
    newInfo.access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
                   | VK_ACCESS_TRANSFER_WRITE_BIT
                   | VK_ACCESS_SHADER_READ_BIT;
    newInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
    newInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    m_resolveImage = m_device->createImage(newInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

    DxvkImageViewCreateInfo viewInfo;
    viewInfo.type = VK_IMAGE_VIEW_TYPE_2D;
    viewInfo.format = info.format;
    viewInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
    viewInfo.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
    viewInfo.minLevel  = 0;
    viewInfo.numLevels = 1;
    viewInfo.minLayer  = 0;
    viewInfo.numLayers = 1;
    m_resolveView = m_device->createImageView(m_resolveImage, viewInfo);
  }

  void DxvkSwapchainBlitter::destroyResolveImage() {
    m_resolveImage = nullptr;
    m_resolveView = nullptr;
  }

}


code:
#pragma once

#include "../dxvk/dxvk_device.h"
#include "../dxvk/dxvk_context.h"

namespace dxvk {

  /**
   * \brief Gamma control point
   */
  struct DxvkGammaCp {
    uint16_t r, g, b, a;
  };

  /**
   * \brief Swap chain blitter
   *
   * Provides common rendering code for blitting
   * rendered images to a swap chain image.
   */
  class DxvkSwapchainBlitter : public RcObject {

  public:

    DxvkSwapchainBlitter(const Rc<DxvkDevice>& device);
    ~DxvkSwapchainBlitter();

    /**
     * \brief Records presentation commands
     *
     * \param [in] ctx Context
     * \param [in] dstView Swap chain image view
     * \param [in] srcView Image to present
     * \param [in] dstRect Destination rectangle
     * \param [in] srcRect Back buffer rectangle
     */
    void presentImage(
            DxvkContext*        ctx,
      const Rc<DxvkImageView>&  dstView,
            VkRect2D            dstRect,
      const Rc<DxvkImageView>&  srcView,
            VkRect2D            srcRect);

    /**
     * \brief Sets gamma ramp
     *
     * If the number of control points is non-zero, this
     * will create a texture containing a gamma ramp that
     * will be used for presentation.
     * \param [in] cpCount Number of control points
     * \param [in] cpData Control point data
     */
    void setGammaRamp(
            uint32_t            cpCount,
      const DxvkGammaCp*        cpData);

  private:

    enum BindingIds : uint32_t {
      Image = 0,
      Gamma = 1,
    };

    struct PresenterArgs {
      VkOffset2D srcOffset;
      union {
        VkExtent2D srcExtent;
        VkOffset2D dstOffset;
      };
    };

    Rc<DxvkDevice>      m_device;

    Rc<DxvkShader>      m_fsCopy;
    Rc<DxvkShader>      m_fsBlit;
    Rc<DxvkShader>      m_fsResolve;
    Rc<DxvkShader>      m_vs;

    Rc<DxvkBuffer>      m_gammaBuffer;
    Rc<DxvkImage>       m_gammaImage;
    Rc<DxvkImageView>   m_gammaView;
    uint32_t            m_gammaCpCount = 0;
    bool                m_gammaDirty = false;
    DxvkBufferSliceHandle m_gammaSlice = { };

    Rc<DxvkImage>       m_resolveImage;
    Rc<DxvkImageView>   m_resolveView;

    Rc<DxvkSampler>     m_samplerPresent;
    Rc<DxvkSampler>     m_samplerGamma;

    void draw(
            DxvkContext*        ctx,
      const Rc<DxvkShader>&     fs,
      const Rc<DxvkImageView>&  dstView,
            VkRect2D            dstRect,
      const Rc<DxvkImageView>&  srcView,
            VkRect2D            srcRect);

    void resolve(
            DxvkContext*        ctx,
      const Rc<DxvkImageView>&  dstView,
      const Rc<DxvkImageView>&  srcView);

    void updateGammaTexture(DxvkContext* ctx);

    void createSampler();

    void createShaders();

    void createResolveImage(
      const DxvkImageCreateInfo&  info);

    void destroyResolveImage();

  };

}


code:
#include "dxvk_device.h"

namespace dxvk {

  DxvkUnboundResources::DxvkUnboundResources(DxvkDevice* dev)
  : m_sampler       (createSampler(dev)),
    m_buffer        (createBuffer(dev)),
    m_bufferView    (createBufferView(dev, m_buffer)),
    m_image1D       (createImage(dev, VK_IMAGE_TYPE_1D, 1)),
    m_image2D       (createImage(dev, VK_IMAGE_TYPE_2D, 6)),
    m_image3D       (createImage(dev, VK_IMAGE_TYPE_3D, 1)),
    m_viewsSampled  (createImageViews(dev, VK_FORMAT_R32_SFLOAT)),
    m_viewsStorage  (createImageViews(dev, VK_FORMAT_R32_UINT)) {

  }

  DxvkUnboundResources::~DxvkUnboundResources() {

  }

  void DxvkUnboundResources::clearResources(DxvkDevice* dev) {
    const Rc<DxvkContext> ctx = dev->createContext();
    ctx->beginRecording(dev->createCommandList());

    this->clearBuffer(ctx, m_buffer);
    this->clearImage(ctx, m_image1D);
    this->clearImage(ctx, m_image2D);
    this->clearImage(ctx, m_image3D);

    dev->submitCommandList(
      ctx->endRecording(),
      VK_NULL_HANDLE,
      VK_NULL_HANDLE);
  }

  Rc<DxvkSampler> DxvkUnboundResources::createSampler(DxvkDevice* dev) {
    DxvkSamplerCreateInfo info;
    info.minFilter      = VK_FILTER_LINEAR;
    info.magFilter      = VK_FILTER_LINEAR;
    info.mipmapMode     = VK_SAMPLER_MIPMAP_MODE_LINEAR;
    info.mipmapLodBias  = 0.0f;
    info.mipmapLodMin   = -256.0f;
    info.mipmapLodMax   =  256.0f;
    info.useAnisotropy  = VK_FALSE;
    info.maxAnisotropy  = 1.0f;
    info.addressModeU   = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    info.addressModeV   = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    info.addressModeW   = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    info.compareToDepth = VK_FALSE;
    info.compareOp      = VK_COMPARE_OP_NEVER;
    info.borderColor    = VkClearColorValue();
    info.usePixelCoord  = VK_FALSE;
    info.nonSeamless    = VK_FALSE;

    return dev->createSampler(info);
  }

  Rc<DxvkBuffer> DxvkUnboundResources::createBuffer(DxvkDevice* dev) {
    DxvkBufferCreateInfo info;
    info.size       = MaxUniformBufferSize;
    info.usage      = VK_BUFFER_USAGE_TRANSFER_DST_BIT
                    | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
                    | VK_BUFFER_USAGE_INDEX_BUFFER_BIT
                    | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
                    | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
                    | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT
                    | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT
                    | VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
    info.stages     = VK_PIPELINE_STAGE_TRANSFER_BIT
                    | dev->getShaderPipelineStages();
    info.access     = VK_ACCESS_UNIFORM_READ_BIT
                    | VK_ACCESS_SHADER_READ_BIT
                    | VK_ACCESS_SHADER_WRITE_BIT;

    return dev->createBuffer(info,
      VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
  }

  Rc<DxvkBufferView> DxvkUnboundResources::createBufferView(
          DxvkDevice*     dev,
    const Rc<DxvkBuffer>& buffer) {
    DxvkBufferViewCreateInfo info;
    info.format      = VK_FORMAT_R32_UINT;
    info.rangeOffset = 0;
    info.rangeLength = buffer->info().size;

    return dev->createBufferView(buffer, info);
  }

  Rc<DxvkImage> DxvkUnboundResources::createImage(
          DxvkDevice*     dev,
          VkImageType     type,
          uint32_t        layers) {
    DxvkImageCreateInfo info;
    info.type        = type;
    info.format      = VK_FORMAT_R32_UINT;
    info.flags       = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
    info.sampleCount = VK_SAMPLE_COUNT_1_BIT;
    info.extent      = { 1, 1, 1 };
    info.numLayers   = layers;
    info.mipLevels   = 1;
    info.usage       = VK_IMAGE_USAGE_TRANSFER_DST_BIT
                     | VK_IMAGE_USAGE_SAMPLED_BIT
                     | VK_IMAGE_USAGE_STORAGE_BIT;
    info.stages      = VK_PIPELINE_STAGE_TRANSFER_BIT
                     | dev->getShaderPipelineStages();
    info.access      = VK_ACCESS_SHADER_READ_BIT;
    info.layout      = VK_IMAGE_LAYOUT_GENERAL;
    info.tiling      = VK_IMAGE_TILING_OPTIMAL;

    if (type == VK_IMAGE_TYPE_2D)
      info.flags       |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;

    return dev->createImage(info,
      VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
  }

  Rc<DxvkImageView> DxvkUnboundResources::createImageView(
          DxvkDevice*     dev,
    const Rc<DxvkImage>&  image,
          VkFormat        format,
          VkImageViewType type,
          uint32_t        layers) {
    DxvkImageViewCreateInfo info;
    info.type         = type;
    info.format       = format;
    info.usage        = VK_IMAGE_USAGE_SAMPLED_BIT
                      | VK_IMAGE_USAGE_STORAGE_BIT;
    info.aspect       = VK_IMAGE_ASPECT_COLOR_BIT;
    info.minLevel     = 0;
    info.numLevels    = 1;
    info.minLayer     = 0;
    info.numLayers    = layers;
    info.swizzle      = VkComponentMapping {
      VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_ZERO,
      VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_ZERO };

    return dev->createImageView(image, info);
  }

  DxvkUnboundResources::UnboundViews DxvkUnboundResources::createImageViews(DxvkDevice* dev, VkFormat format) {
    UnboundViews result;
    result.view1D      = createImageView(dev, m_image1D, format, VK_IMAGE_VIEW_TYPE_1D,         1);
    result.view1DArr   = createImageView(dev, m_image1D, format, VK_IMAGE_VIEW_TYPE_1D_ARRAY,   1);
    result.view2D      = createImageView(dev, m_image2D, format, VK_IMAGE_VIEW_TYPE_2D,         1);
    result.view2DArr   = createImageView(dev, m_image2D, format, VK_IMAGE_VIEW_TYPE_2D_ARRAY,   1);
    result.viewCube    = createImageView(dev, m_image2D, format, VK_IMAGE_VIEW_TYPE_CUBE,       6);
    result.viewCubeArr = createImageView(dev, m_image2D, format, VK_IMAGE_VIEW_TYPE_CUBE_ARRAY, 6);
    result.view3D      = createImageView(dev, m_image3D, format, VK_IMAGE_VIEW_TYPE_3D,         1);
    return result;
  }

  const DxvkImageView* DxvkUnboundResources::getImageView(VkImageViewType type, bool sampled) const {
    auto views = sampled ? &m_viewsSampled : &m_viewsStorage;

    switch (type) {
      case VK_IMAGE_VIEW_TYPE_1D:         return views->view1D.ptr();
      case VK_IMAGE_VIEW_TYPE_1D_ARRAY:   return views->view1DArr.ptr();
      // When implicit samplers are unbound -- we assume 2D in the shader.
      case VK_IMAGE_VIEW_TYPE_MAX_ENUM:
      case VK_IMAGE_VIEW_TYPE_2D:         return views->view2D.ptr();
      case VK_IMAGE_VIEW_TYPE_2D_ARRAY:   return views->view2DArr.ptr();
      case VK_IMAGE_VIEW_TYPE_CUBE:       return views->viewCube.ptr();
      case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY: return views->viewCubeArr.ptr();
      case VK_IMAGE_VIEW_TYPE_3D:         return views->view3D.ptr();
      default:                            return nullptr;
    }
  }

  void DxvkUnboundResources::clearBuffer(
    const Rc<DxvkContext>&  ctx,
    const Rc<DxvkBuffer>&   buffer) {
    ctx->initBuffer(buffer);
  }

  void DxvkUnboundResources::clearImage(
    const Rc<DxvkContext>&  ctx,
    const Rc<DxvkImage>&    image) {
    ctx->initImage(image,
      VkImageSubresourceRange {
        VK_IMAGE_ASPECT_COLOR_BIT,
        0, image->info().mipLevels,
        0, image->info().numLayers },
      VK_IMAGE_LAYOUT_UNDEFINED);
  }

}


code:
#pragma once

#include "dxvk_buffer.h"
#include "dxvk_image.h"
#include "dxvk_sampler.h"

namespace dxvk {

  class DxvkContext;

  /**
   * \brief Unbound resources
   *
   * Creates dummy resources that will be used
   * for descriptor sets when the client API did
   * not bind a compatible resource to a slot.
   */
  class DxvkUnboundResources {

  public:

    DxvkUnboundResources(DxvkDevice* dev);
    ~DxvkUnboundResources();

    /**
     * \brief Dummy buffer handle
     *
     * Returns a handle to a buffer filled
     * with zeroes. Use for unbound vertex
     * and index buffers.
     * \returns Dummy buffer handle
     */
    VkBuffer bufferHandle() const {
      return m_buffer->getSliceHandle().handle;
    }

    /**
     * \brief Dummy buffer descriptor
     *
     * Points to a small buffer filled with zeroes.
     * Do not write to this buffer, and do not use
     * it if out-of-bounds read access is possible.
     * \returns Dummy buffer descriptor
     */
    VkDescriptorBufferInfo bufferDescriptor() const {
      auto slice = m_buffer->getSliceHandle();

      VkDescriptorBufferInfo result;
      result.buffer = slice.handle;
      result.offset = slice.offset;
      result.range  = slice.length;
      return result;
    }

    /**
     * \brief Dummy buffer view
     *
     * Returns an \c R32_UINT view into the
     * dummy buffer, which will contain one
     * element with undefined value.
     * \returns Dummy buffer view
     */
    VkBufferView bufferViewDescriptor() const {
      return m_bufferView->handle();
    }

    /**
     * \brief Dummy sampler descriptor
     *
     * Points to a sampler which was created with
     * reasonable default values. Client APIs may
     * still require different behaviour.
     * \returns Dummy sampler descriptor
     */
    VkDescriptorImageInfo samplerDescriptor() const {
      VkDescriptorImageInfo result;
      result.sampler     = m_sampler->handle();
      result.imageView   = VK_NULL_HANDLE;
      result.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
      return result;
    }

    /**
     * \brief Dummy combined image sampler descriptor
     *
     * Contains both an image view and a sampler
     * descriptor for the given image view type.
     * \param [in] type Image view type
     * \returns Dummy image view descriptor
     */
    VkDescriptorImageInfo imageSamplerDescriptor(VkImageViewType type) const {
      auto view = getImageView(type, true);

      VkDescriptorImageInfo result;
      result.sampler     = m_sampler->handle();
      result.imageView   = view->handle();
      result.imageLayout = view->imageInfo().layout;
      return result;
    }

    /**
     * \brief Dummy image view descriptor
     *
     * Points to an image view which, instead of
     * reading image data, will return zeroes for
     * all components unconditionally.
     * \param [in] type Image view type
     * \param [in] sampled Format selector
     * \returns Dummy image view descriptor
     */
    VkDescriptorImageInfo imageViewDescriptor(VkImageViewType type, bool sampled) const {
      auto view = getImageView(type, sampled);

      VkDescriptorImageInfo result;
      result.sampler     = VK_NULL_HANDLE;
      result.imageView   = view->handle();
      result.imageLayout = view->imageInfo().layout;
      return result;
    }

    /**
     * \brief Clears the resources
     *
     * Initializes all images and buffers to zero.
     * \param [in] dev The DXVK device handle
     */
    void clearResources(DxvkDevice* dev);

  private:

    struct UnboundViews {
      Rc<DxvkImageView> view1D;
      Rc<DxvkImageView> view1DArr;
      Rc<DxvkImageView> view2D;
      Rc<DxvkImageView> view2DArr;
      Rc<DxvkImageView> viewCube;
      Rc<DxvkImageView> viewCubeArr;
      Rc<DxvkImageView> view3D;
    };

    Rc<DxvkSampler> m_sampler;

    Rc<DxvkBuffer>     m_buffer;
    Rc<DxvkBufferView> m_bufferView;

    Rc<DxvkImage> m_image1D;
    Rc<DxvkImage> m_image2D;
    Rc<DxvkImage> m_image3D;

    UnboundViews m_viewsSampled;
    UnboundViews m_viewsStorage;

    Rc<DxvkSampler> createSampler(DxvkDevice* dev);

    Rc<DxvkBuffer> createBuffer(DxvkDevice* dev);

    Rc<DxvkBufferView> createBufferView(
            DxvkDevice*     dev,
      const Rc<DxvkBuffer>& buffer);

    Rc<DxvkImage> createImage(
            DxvkDevice*     dev,
            VkImageType     type,
            uint32_t        layers);

    Rc<DxvkImageView> createImageView(
            DxvkDevice*     dev,
      const Rc<DxvkImage>&  image,
            VkFormat        format,
            VkImageViewType type,
            uint32_t        layers);

    UnboundViews createImageViews(
            DxvkDevice*     dev,
            VkFormat        format);

    const DxvkImageView* getImageView(
            VkImageViewType type,
            bool            sampled) const;

    void clearBuffer(
      const Rc<DxvkContext>&  ctx,
      const Rc<DxvkBuffer>&   buffer);

    void clearImage(
      const Rc<DxvkContext>&  ctx,
      const Rc<DxvkImage>&    image);

  };

}


code:
#include <cstring>

#include "dxvk_format.h"
#include "dxvk_util.h"

namespace dxvk::util {

  uint32_t computeMipLevelCount(VkExtent3D imageSize) {
    uint32_t maxDim = std::max(imageSize.width, imageSize.height);
             maxDim = std::max(imageSize.depth, maxDim);
    uint32_t mipCnt = 0;

    while (maxDim > 0) {
      mipCnt += 1;
      maxDim /= 2;
    }

    return mipCnt;
  }

  void packImageData(
          void*             dstBytes,
    const void*             srcBytes,
          VkExtent3D        blockCount,
          VkDeviceSize      blockSize,
          VkDeviceSize      pitchPerRow,
          VkDeviceSize      pitchPerLayer) {
    auto dstData = reinterpret_cast<      char*>(dstBytes);
    auto srcData = reinterpret_cast<const char*>(srcBytes);

    const VkDeviceSize bytesPerRow   = blockCount.width  * blockSize;
    const VkDeviceSize bytesPerLayer = blockCount.height * bytesPerRow;
    const VkDeviceSize bytesTotal    = blockCount.depth  * bytesPerLayer;

    const bool directCopy = ((bytesPerRow   == pitchPerRow  ) || (blockCount.height == 1))
                         && ((bytesPerLayer == pitchPerLayer) || (blockCount.depth  == 1));

    if (directCopy) {
      std::memcpy(dstData, srcData, bytesTotal);
    } else {
      for (uint32_t i = 0; i < blockCount.depth; i++) {
        for (uint32_t j = 0; j < blockCount.height; j++) {
          std::memcpy(
            dstData + j * bytesPerRow,
            srcData + j * pitchPerRow,
            bytesPerRow);
        }

        srcData += pitchPerLayer;
        dstData += bytesPerLayer;
      }
    }
  }

  void packImageData(
          void*             dstBytes,
    const void*             srcBytes,
          VkDeviceSize      srcRowPitch,
          VkDeviceSize      srcSlicePitch,
          VkDeviceSize      dstRowPitchIn,
          VkDeviceSize      dstSlicePitchIn,
          VkImageType       imageType,
          VkExtent3D        imageExtent,
          uint32_t          imageLayers,
    const DxvkFormatInfo*   formatInfo,
          VkImageAspectFlags aspectMask) {
    for (uint32_t i = 0; i < imageLayers; i++) {
      auto dstData = reinterpret_cast<      char*>(dstBytes);
      auto srcData = reinterpret_cast<const char*>(srcBytes);

      for (auto aspects = aspectMask; aspects; ) {
        auto aspect = vk::getNextAspect(aspects);
        auto extent = imageExtent;
        auto elementSize = formatInfo->elementSize;

        if (formatInfo->flags.test(DxvkFormatFlag::MultiPlane)) {
          auto plane = &formatInfo->planes[vk::getPlaneIndex(aspect)];
          extent.width  /= plane->blockSize.width;
          extent.height /= plane->blockSize.height;
          elementSize = plane->elementSize;
        }

        auto blockCount = computeBlockCount(extent, formatInfo->blockSize);

        VkDeviceSize bytesPerRow   = blockCount.width  * elementSize;
        VkDeviceSize bytesPerSlice = blockCount.height * bytesPerRow;
        VkDeviceSize bytesTotal    = blockCount.depth  * bytesPerSlice;

        VkDeviceSize dstRowPitch   = dstRowPitchIn   ? dstRowPitchIn   : bytesPerRow;
        VkDeviceSize dstSlicePitch = dstSlicePitchIn ? dstSlicePitchIn : bytesPerSlice;

        const bool directCopy = ((bytesPerRow   == srcRowPitch   && bytesPerRow   == dstRowPitch  ) || (blockCount.height == 1))
                             && ((bytesPerSlice == srcSlicePitch && bytesPerSlice == dstSlicePitch) || (blockCount.depth  == 1));

        if (directCopy) {
          std::memcpy(dstData, srcData, bytesTotal);

          switch (imageType) {
            case VK_IMAGE_TYPE_1D:
              srcData += srcRowPitch;
              dstData += dstRowPitch;
              break;
            case VK_IMAGE_TYPE_2D:
              srcData += blockCount.height * srcRowPitch;
              dstData += blockCount.height * dstRowPitch;
              break;
            case VK_IMAGE_TYPE_3D:
              srcData += blockCount.depth * srcSlicePitch;
              dstData += blockCount.depth * dstSlicePitch;
              break;
            default: ;
          }
        } else {
          for (uint32_t i = 0; i < blockCount.depth; i++) {
            for (uint32_t j = 0; j < blockCount.height; j++) {
              std::memcpy(
                dstData + j * dstRowPitch,
                srcData + j * srcRowPitch,
                bytesPerRow);
            }

            switch (imageType) {
              case VK_IMAGE_TYPE_1D:
                srcData += srcRowPitch;
                dstData += dstRowPitch;
                break;
              case VK_IMAGE_TYPE_2D:
                srcData += blockCount.height * srcRowPitch;
                dstData += blockCount.height * dstRowPitch;
                break;
              case VK_IMAGE_TYPE_3D:
                srcData += srcSlicePitch;
                dstData += dstSlicePitch;
                break;
              default: ;
            }
          }
        }
      }
    }
  }

  VkDeviceSize computeImageDataSize(VkFormat format, VkExtent3D extent) {
    const DxvkFormatInfo* formatInfo = imageFormatInfo(format);

    VkDeviceSize size = 0;

    for (auto aspects = formatInfo->aspectMask; aspects; ) {
      auto aspect = vk::getNextAspect(aspects);
      auto elementSize = formatInfo->elementSize;
      auto planeExtent = extent;

      if (formatInfo->flags.test(DxvkFormatFlag::MultiPlane)) {
        auto plane = &formatInfo->planes[vk::getPlaneIndex(aspect)];
        planeExtent.width  /= plane->blockSize.width;
        planeExtent.height /= plane->blockSize.height;
        elementSize = plane->elementSize;
      }

      size += elementSize * flattenImageExtent(computeBlockCount(planeExtent, formatInfo->blockSize));
    }

    return size;
  }

  static VkColorComponentFlags remapComponentFlag(
          VkColorComponentFlags       mask,
          VkComponentSwizzle          swizzle,
          VkColorComponentFlagBits    identity) {
    VkColorComponentFlags bit;

    switch (swizzle) {
      case VK_COMPONENT_SWIZZLE_IDENTITY: bit = identity;                 break;
      case VK_COMPONENT_SWIZZLE_R:        bit = VK_COLOR_COMPONENT_R_BIT; break;
      case VK_COMPONENT_SWIZZLE_G:        bit = VK_COLOR_COMPONENT_G_BIT; break;
      case VK_COMPONENT_SWIZZLE_B:        bit = VK_COLOR_COMPONENT_B_BIT; break;
      case VK_COMPONENT_SWIZZLE_A:        bit = VK_COLOR_COMPONENT_A_BIT; break;
      default:                            bit = 0; /* SWIZZLE_ZERO, SWIZZLE_ONE */
    }

    return (mask & bit) ? identity : 0;
  }

  VkColorComponentFlags remapComponentMask(
          VkColorComponentFlags       mask,
          VkComponentMapping          mapping) {
    VkColorComponentFlags result = 0;
    result |= remapComponentFlag(mask, mapping.r, VK_COLOR_COMPONENT_R_BIT);
    result |= remapComponentFlag(mask, mapping.g, VK_COLOR_COMPONENT_G_BIT);
    result |= remapComponentFlag(mask, mapping.b, VK_COLOR_COMPONENT_B_BIT);
    result |= remapComponentFlag(mask, mapping.a, VK_COLOR_COMPONENT_A_BIT);
    return result;
  }

  static VkComponentSwizzle findComponentSwizzle(
          VkComponentSwizzle          swizzle,
          VkComponentSwizzle          identity,
          VkComponentMapping          mapping) {
    if (identity == VK_COMPONENT_SWIZZLE_IDENTITY)
      return VK_COMPONENT_SWIZZLE_IDENTITY;

    if (mapping.r == swizzle)
      return VK_COMPONENT_SWIZZLE_R;
    if (mapping.g == swizzle)
      return VK_COMPONENT_SWIZZLE_G;
    if (mapping.b == swizzle)
      return VK_COMPONENT_SWIZZLE_B;
    if (mapping.a == swizzle)
      return VK_COMPONENT_SWIZZLE_A;

    return VK_COMPONENT_SWIZZLE_ZERO;
  }

  VkComponentMapping invertComponentMapping(VkComponentMapping mapping) {
    VkComponentMapping result;
    result.r = findComponentSwizzle(VK_COMPONENT_SWIZZLE_R, mapping.r, mapping);
    result.g = findComponentSwizzle(VK_COMPONENT_SWIZZLE_G, mapping.g, mapping);
    result.b = findComponentSwizzle(VK_COMPONENT_SWIZZLE_B, mapping.b, mapping);
    result.a = findComponentSwizzle(VK_COMPONENT_SWIZZLE_A, mapping.a, mapping);
    return result;
  }

  static VkComponentMapping normalizeComponentMapping(
          VkComponentMapping          mapping) {
    mapping.r = mapping.r == VK_COMPONENT_SWIZZLE_IDENTITY ? VK_COMPONENT_SWIZZLE_R : mapping.r;
    mapping.g = mapping.g == VK_COMPONENT_SWIZZLE_IDENTITY ? VK_COMPONENT_SWIZZLE_G : mapping.g;
    mapping.b = mapping.b == VK_COMPONENT_SWIZZLE_IDENTITY ? VK_COMPONENT_SWIZZLE_B : mapping.b;
    mapping.a = mapping.a == VK_COMPONENT_SWIZZLE_IDENTITY ? VK_COMPONENT_SWIZZLE_A : mapping.a;
    return mapping;
  }

  static VkComponentSwizzle resolveComponentSwizzle(
          VkComponentSwizzle          swizzle,
          VkComponentMapping          dstMapping,
          VkComponentMapping          srcMapping) {
    VkComponentSwizzle dstSwizzle = VK_COMPONENT_SWIZZLE_IDENTITY;
    if (dstMapping.r == swizzle) dstSwizzle = VK_COMPONENT_SWIZZLE_R;
    if (dstMapping.g == swizzle) dstSwizzle = VK_COMPONENT_SWIZZLE_G;
    if (dstMapping.b == swizzle) dstSwizzle = VK_COMPONENT_SWIZZLE_B;
    if (dstMapping.a == swizzle) dstSwizzle = VK_COMPONENT_SWIZZLE_A;

    switch (dstSwizzle) {
      case VK_COMPONENT_SWIZZLE_R: return srcMapping.r;
      case VK_COMPONENT_SWIZZLE_G: return srcMapping.g;
      case VK_COMPONENT_SWIZZLE_B: return srcMapping.b;
      case VK_COMPONENT_SWIZZLE_A: return srcMapping.a;
      default: return VK_COMPONENT_SWIZZLE_IDENTITY;
    }
  }

  VkComponentMapping resolveSrcComponentMapping(
          VkComponentMapping          dstMapping,
          VkComponentMapping          srcMapping) {
    dstMapping = normalizeComponentMapping(dstMapping);

    VkComponentMapping result;
    result.r = resolveComponentSwizzle(VK_COMPONENT_SWIZZLE_R, dstMapping, srcMapping);
    result.g = resolveComponentSwizzle(VK_COMPONENT_SWIZZLE_G, dstMapping, srcMapping);
    result.b = resolveComponentSwizzle(VK_COMPONENT_SWIZZLE_B, dstMapping, srcMapping);
    result.a = resolveComponentSwizzle(VK_COMPONENT_SWIZZLE_A, dstMapping, srcMapping);
    return result;
  }

  bool isIdentityMapping(
          VkComponentMapping          mapping) {
    return (mapping.r == VK_COMPONENT_SWIZZLE_R || mapping.r == VK_COMPONENT_SWIZZLE_IDENTITY)
        && (mapping.g == VK_COMPONENT_SWIZZLE_G || mapping.g == VK_COMPONENT_SWIZZLE_IDENTITY)
        && (mapping.b == VK_COMPONENT_SWIZZLE_B || mapping.b == VK_COMPONENT_SWIZZLE_IDENTITY)
        && (mapping.a == VK_COMPONENT_SWIZZLE_A || mapping.a == VK_COMPONENT_SWIZZLE_IDENTITY);
  }

  uint32_t getComponentIndex(
          VkComponentSwizzle          component,
          uint32_t                    identity) {
    switch (component) {
      case VK_COMPONENT_SWIZZLE_R: return 0;
      case VK_COMPONENT_SWIZZLE_G: return 1;
      case VK_COMPONENT_SWIZZLE_B: return 2;
      case VK_COMPONENT_SWIZZLE_A: return 3;
      default: return identity; /* identity, zero, one */
    }
  }

  VkClearColorValue swizzleClearColor(
          VkClearColorValue           color,
          VkComponentMapping          mapping) {
    VkClearColorValue result;
    auto swizzles = &mapping.r;

    for (uint32_t i = 0; i < 4; i++) {
      uint32_t index = getComponentIndex(swizzles[i], i);
      result.uint32[i] = color.uint32[index];
    }

    return result;
  }

  bool isBlendConstantBlendFactor(VkBlendFactor factor) {
    return factor == VK_BLEND_FACTOR_CONSTANT_COLOR
        || factor == VK_BLEND_FACTOR_CONSTANT_ALPHA
        || factor == VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR
        || factor == VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA;
  }

  bool isDualSourceBlendFactor(VkBlendFactor factor) {
    return factor == VK_BLEND_FACTOR_SRC1_COLOR
        || factor == VK_BLEND_FACTOR_SRC1_ALPHA
        || factor == VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR
        || factor == VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA;
  }

}