code:
#pragma once

#include "dxvk_include.h"

namespace dxvk::util {

  /**
   * \brief Gets pipeline stage flags for shader stages
   *
   * \param [in] shaderStages Shader stage flags
   * \returns Corresponding pipeline stage flags
   */
  inline VkPipelineStageFlags pipelineStages(
          VkShaderStageFlags shaderStages) {
    return (shaderStages & VK_SHADER_STAGE_ALL_GRAPHICS) << 3
         | (shaderStages & VK_SHADER_STAGE_COMPUTE_BIT) << 6;
  }

  /**
   * \brief Computes number of mip levels for an image
   *
   * \param [in] imageSize Size of the image
   * \returns Number of mipmap layers
   */
  uint32_t computeMipLevelCount(VkExtent3D imageSize);

  /**
   * \brief Writes tightly packed image data to a buffer
   *
   * \param [in] dstBytes Destination buffer pointer
   * \param [in] srcBytes Pointer to source data
   * \param [in] blockCount Number of blocks to copy
   * \param [in] blockSize Number of bytes per block
   * \param [in] pitchPerRow Number of bytes between rows
   * \param [in] pitchPerLayer Number of bytes between layers
   */
  void packImageData(
          void*             dstBytes,
    const void*             srcBytes,
          VkExtent3D        blockCount,
          VkDeviceSize      blockSize,
          VkDeviceSize      pitchPerRow,
          VkDeviceSize      pitchPerLayer);

  /**
   * \brief Repacks image data to a buffer
   *
   * Note that passing destination pitches of 0 means that the data is
   * tightly packed, while a source pitch of 0 will not show this behaviour
   * in order to match client API behaviour for initialization.
   * \param [in] dstBytes Destination buffer pointer
   * \param [in] srcBytes Pointer to source data
   * \param [in] srcRowPitch Number of bytes between rows to read
   * \param [in] srcSlicePitch Number of bytes between layers to read
   * \param [in] dstRowPitch Number of bytes between rows to write
   * \param [in] dstSlicePitch Number of bytes between layers to write
   * \param [in] imageType Image type (2D, 3D etc)
   * \param [in] imageExtent Image extent, in pixels
   * \param [in] imageLayers Image layer count
   * \param [in] formatInfo Image format info
   * \param [in] aspectMask Image aspects to pack
   */
  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);

  /**
   * \brief Computes minimum extent
   *
   * \param [in] a First value
   * \param [in] b Second value
   * \returns Component-wise \c min
   */
  inline VkExtent3D minExtent3D(VkExtent3D a, VkExtent3D b) {
    return VkExtent3D {
      std::min(a.width,  b.width),
      std::min(a.height, b.height),
      std::min(a.depth,  b.depth) };
  }

  /**
   * \brief Checks whether an offset is block-aligned
   *
   * An offset is considered block-aligned if it is
   * a multiple of the block size. Only non-negative
   * offset values are valid.
   * \param [in] offset The offset to check
   * \param [in] blockSize Block size
   * \returns \c true if \c offset is aligned
   */
  inline bool isBlockAligned(VkOffset3D offset, VkExtent3D blockSize) {
    return (offset.x % blockSize.width  == 0)
        && (offset.y % blockSize.height == 0)
        && (offset.z % blockSize.depth  == 0);
  }

  /**
   * \brief Checks whether an offset and extent are block-aligned
   *
   * A block-aligned extent can be used for image copy
   * operations that involve block-compressed images.
   * \param [in] offset The base offset
   * \param [in] extent The extent to check
   * \param [in] blockSize Compressed block size
   * \param [in] imageSize Image size
   * \returns \c true if all components of \c extent
   *          are aligned or touch the image border.
   */
  inline bool isBlockAligned(VkOffset3D offset, VkExtent3D extent, VkExtent3D blockSize, VkExtent3D imageSize) {
    return ((extent.width  % blockSize.width  == 0) || (uint32_t(offset.x + extent.width)  == imageSize.width))
        && ((extent.height % blockSize.height == 0) || (uint32_t(offset.y + extent.height) == imageSize.height))
        && ((extent.depth  % blockSize.depth  == 0) || (uint32_t(offset.z + extent.depth)  == imageSize.depth))
        && isBlockAligned(offset, blockSize);
  }

  /**
   * \brief Computes mip level extent
   *
   * \param [in] size Base mip level extent
   * \param [in] level mip level to compute
   * \returns Extent of the given mip level
   */
  inline VkExtent3D computeMipLevelExtent(VkExtent3D size, uint32_t level) {
    size.width  = std::max(1u, size.width  >> level);
    size.height = std::max(1u, size.height >> level);
    size.depth  = std::max(1u, size.depth  >> level);
    return size;
  }

  /**
   * \brief Computes offset in a given mip level
   *
   * \param [in] size Base mip level offset
   * \param [in] level mip level to compute
   * \returns Offset on the given mip level
   */
  inline VkOffset3D computeMipLevelOffset(VkOffset3D offset, uint32_t level) {
    offset.x  = offset.x >> level;
    offset.y  = offset.y >> level;
    offset.z  = offset.z >> level;
    return offset;
  }

  /**
   * \brief Computes mip level extent
   *
   * This function variant takes into account planar formats.
   * \param [in] size Base mip level extent
   * \param [in] level Mip level to compute
   * \param [in] format Image format
   * \param [in] aspect Image aspect to consider
   * \returns Extent of the given mip level
   */
  inline VkExtent3D computeMipLevelExtent(VkExtent3D size, uint32_t level, VkFormat format, VkImageAspectFlags aspect) {
    if (unlikely(!(aspect & (VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)))) {
      auto plane = &imageFormatInfo(format)->planes[vk::getPlaneIndex(aspect)];
      size.width  /= plane->blockSize.width;
      size.height /= plane->blockSize.height;
    }

    size.width  = std::max(1u, size.width  >> level);
    size.height = std::max(1u, size.height >> level);
    size.depth  = std::max(1u, size.depth  >> level);
    return size;
  }

  /**
   * \brief Computes block offset for compressed images
   *
   * Convenience function to compute the block position
   * within a compressed image based on the block size.
   * \param [in] offset The offset
   * \param [in] blockSize Size of a pixel block
   * \returns The block offset
   */
  inline VkOffset3D computeBlockOffset(VkOffset3D offset, VkExtent3D blockSize) {
    return VkOffset3D {
      offset.x / int32_t(blockSize.width),
      offset.y / int32_t(blockSize.height),
      offset.z / int32_t(blockSize.depth) };
  }

  /**
   * \brief Computes block count for compressed images
   *
   * Convenience function to compute the size, in
   * blocks, of compressed images subresources.
   * \param [in] extent The image size
   * \param [in] blockSize Size of a pixel block
   * \returns Number of blocks in the image
   */
  inline VkExtent3D computeBlockCount(VkExtent3D extent, VkExtent3D blockSize) {
    return VkExtent3D {
      (extent.width  + blockSize.width  - 1) / blockSize.width,
      (extent.height + blockSize.height - 1) / blockSize.height,
      (extent.depth  + blockSize.depth  - 1) / blockSize.depth };
  }

  /**
   * \brief Computes block count for compressed images
   *
   * Given an aligned offset, this computes
   * Convenience function to compute the size, in
   * blocks, of compressed images subresources.
   * \param [in] extent The image size
   * \param [in] blockSize Size of a pixel block
   * \returns Number of blocks in the image
   */
  inline VkExtent3D computeMaxBlockCount(VkOffset3D offset, VkExtent3D extent, VkExtent3D blockSize) {
    return VkExtent3D {
      (extent.width  + blockSize.width  - offset.x - 1) / blockSize.width,
      (extent.height + blockSize.height - offset.y - 1) / blockSize.height,
      (extent.depth  + blockSize.depth  - offset.z - 1) / blockSize.depth };
  }

  /**
   * \brief Snaps block-aligned image extent to image edges
   *
   * Fixes up an image extent that is aligned to a compressed
   * block so that it no longer exceeds the given image size.
   * \param [in] offset Aligned pixel offset
   * \param [in] extent Extent to clamp
   * \param [in] imageExtent Image size
   * \returns Number of blocks in the image
   */
  inline VkExtent3D snapExtent3D(VkOffset3D offset, VkExtent3D extent, VkExtent3D imageExtent) {
    return VkExtent3D {
      std::min(extent.width,  imageExtent.width  - uint32_t(offset.x)),
      std::min(extent.height, imageExtent.height - uint32_t(offset.y)),
      std::min(extent.depth,  imageExtent.depth  - uint32_t(offset.z)) };
  }

  /**
   * \brief Computes block extent for compressed images
   *
   * \param [in] blockCount The number of blocks
   * \param [in] blockSize Size of a pixel block
   * \returns Extent of the given blocks
   */
  inline VkExtent3D computeBlockExtent(VkExtent3D blockCount, VkExtent3D blockSize) {
    return VkExtent3D {
      blockCount.width  * blockSize.width,
      blockCount.height * blockSize.height,
      blockCount.depth  * blockSize.depth };
  }

  /**
   * \brief Computes number of pixels or blocks of an image
   *
   * Basically returns the product of width, height and depth.
   * \param [in] extent Image extent, in pixels or blocks
   * \returns Flattened number of pixels or blocks
   */
  inline uint32_t flattenImageExtent(VkExtent3D extent) {
    return extent.width * extent.height * extent.depth;
  }

  /**
   * \brief Checks whether the depth aspect is read-only in a layout
   *
   * \param [in] layout Image layout. Must be a valid depth-stencil attachment laoyut.
   * \returns \c true if the depth aspect for images in this layout is read-only.
   */
  inline bool isDepthReadOnlyLayout(VkImageLayout layout) {
    return layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
        || layout == VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL;
  }

  /**
   * \brief Computes image data size, in bytes
   *
   * Convenience method that can be used to compute the number
   * of bytes required to store image data in a given format.
   * \param [in] format The image format
   * \param [in] extent Image size, in pixels
   * \returns Data size, in bytes
   */
  VkDeviceSize computeImageDataSize(VkFormat format, VkExtent3D extent);

  /**
   * \brief Applies a component mapping to a component mask
   *
   * For each component, the component specified in the mapping
   * is used to look up the flag of the original component mask.
   * If the component mapping is zero or one, the corresponding
   * mask bit will be set to zero.
   * \param [in] mask The original component mask
   * \param [in] mapping Component mapping to apply
   * \returns Remapped component mask
   */
  VkColorComponentFlags remapComponentMask(
          VkColorComponentFlags       mask,
          VkComponentMapping          mapping);

  /**
   * \brief Inverts a component mapping
   *
   * Transforms a component mapping so that components can
   * be mapped back to their original location. Requires
   * that each component is used only once.
   *
   * For example. when given a mapping of (0,0,0,R),
   * this function will return the mapping (A,0,0,0).
   * \returns Inverted component mapping
   */
  VkComponentMapping invertComponentMapping(
          VkComponentMapping          mapping);

  /**
   * \brief Resolves source component mapping
   *
   * Returns the source component mapping after rearranging
   * the destination mapping to be the identity mapping.
   * \param [in] dstMapping Destination mapping
   * \param [in] srcMapping Source mapping
   * \returns Adjusted src component mapping
   */
  VkComponentMapping resolveSrcComponentMapping(
          VkComponentMapping          dstMapping,
          VkComponentMapping          srcMapping);

  bool isIdentityMapping(
          VkComponentMapping          mapping);

  /**
   * \brief Computes component index for a component swizzle
   *
   * \param [in] component The component swizzle
   * \param [in] identity Value for SWIZZLE_IDENTITY
   * \returns Component index
   */
  uint32_t getComponentIndex(
          VkComponentSwizzle          component,
          uint32_t                    identity);

  VkClearColorValue swizzleClearColor(
          VkClearColorValue           color,
          VkComponentMapping          mapping);

  bool isBlendConstantBlendFactor(
          VkBlendFactor               factor);

  bool isDualSourceBlendFactor(
          VkBlendFactor               factor);

}


code:
#include <cstring>

#include "dxvk_hud.h"

namespace dxvk::hud {

  Hud::Hud(
    const Rc<DxvkDevice>& device)
  : m_device        (device),
    m_renderer      (device),
    m_hudItems      (device),
    m_scale         (m_hudItems.getOption<float>("scale", 1.0f)) {
    // Sanitize scaling factor
    if (m_scale < 0.01f)
      m_scale = 1.0f;

    // Set up constant state
    m_rsState.polygonMode       = VK_POLYGON_MODE_FILL;
    m_rsState.cullMode          = VK_CULL_MODE_BACK_BIT;
    m_rsState.frontFace         = VK_FRONT_FACE_CLOCKWISE;
    m_rsState.depthClipEnable   = VK_FALSE;
    m_rsState.depthBiasEnable   = VK_FALSE;
    m_rsState.conservativeMode  = VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT;
    m_rsState.sampleCount       = VK_SAMPLE_COUNT_1_BIT;

    m_blendMode.enableBlending  = VK_TRUE;
    m_blendMode.colorSrcFactor  = VK_BLEND_FACTOR_ONE;
    m_blendMode.colorDstFactor  = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
    m_blendMode.colorBlendOp    = VK_BLEND_OP_ADD;
    m_blendMode.alphaSrcFactor  = VK_BLEND_FACTOR_ONE;
    m_blendMode.alphaDstFactor  = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
    m_blendMode.alphaBlendOp    = VK_BLEND_OP_ADD;
    m_blendMode.writeMask       = VK_COLOR_COMPONENT_R_BIT
                                | VK_COLOR_COMPONENT_G_BIT
                                | VK_COLOR_COMPONENT_B_BIT
                                | VK_COLOR_COMPONENT_A_BIT;

    addItem<HudVersionItem>("version", -1);
    addItem<HudDeviceInfoItem>("devinfo", -1, m_device);
    addItem<HudFpsItem>("fps", -1);
    addItem<HudFrameTimeItem>("frametimes", -1);
    addItem<HudSubmissionStatsItem>("submissions", -1, device);
    addItem<HudDrawCallStatsItem>("drawcalls", -1, device);
    addItem<HudPipelineStatsItem>("pipelines", -1, device);
    addItem<HudMemoryStatsItem>("memory", -1, device);
    addItem<HudCsThreadItem>("cs", -1, device);
    addItem<HudGpuLoadItem>("gpuload", -1, device);
    addItem<HudCompilerActivityItem>("compiler", -1, device);
  }

  Hud::~Hud() {

  }

  void Hud::update() {
    m_hudItems.update();
  }

  void Hud::render(
    const Rc<DxvkContext>&  ctx,
          VkSurfaceFormatKHR surfaceFormat,
          VkExtent2D        surfaceSize) {
    this->setupRendererState(ctx, surfaceFormat, surfaceSize);
    this->renderHudElements(ctx);
    this->resetRendererState(ctx);
  }

  Rc<Hud> Hud::createHud(const Rc<DxvkDevice>& device) {
    return new Hud(device);
  }

  void Hud::setupRendererState(
    const Rc<DxvkContext>&  ctx,
          VkSurfaceFormatKHR surfaceFormat,
          VkExtent2D        surfaceSize) {
    bool isSrgb = imageFormatInfo(surfaceFormat.format)->flags.test(DxvkFormatFlag::ColorSpaceSrgb);

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

    VkRect2D scissor;
    scissor.offset = { 0, 0 };
    scissor.extent = surfaceSize;

    ctx->setViewports(1, &viewport, &scissor);
    ctx->setRasterizerState(m_rsState);
    ctx->setBlendMode(0, m_blendMode);

    ctx->setSpecConstant(VK_PIPELINE_BIND_POINT_GRAPHICS, 0, isSrgb);
    m_renderer.beginFrame(ctx, surfaceSize, m_scale);
  }

  void Hud::resetRendererState(const Rc<DxvkContext>& ctx) {
    ctx->setSpecConstant(VK_PIPELINE_BIND_POINT_GRAPHICS, 0, 0);
  }

  void Hud::renderHudElements(const Rc<DxvkContext>& ctx) {
    m_hudItems.render(m_renderer);
  }

}

repo_name: orochi/dxvk


code:
#pragma once

#include "../dxvk_device.h"

#include "dxvk_hud_item.h"
#include "dxvk_hud_renderer.h"

namespace dxvk::hud {

  /**
   * \brief HUD uniform data
   * Shader data for the HUD.
   */
  struct HudUniformData {
    VkExtent2D surfaceSize;
  };

  /**
   * \brief DXVK HUD
   *
   * Can be used by the presentation backend to
   * display performance and driver information.
   */
  class Hud : public RcObject {

  public:

    Hud(const Rc<DxvkDevice>& device);

    ~Hud();

    /**
     * \brief Update HUD
     *
     * Updates the data to display.
     * Should be called once per frame.
     */
    void update();

    /**
     * \brief Render HUD
     *
     * Renders the HUD to the given context.
     * \param [in] ctx Device context
     * \param [in] surfaceSize Image size, in pixels
     */
    void render(
      const Rc<DxvkContext>&  ctx,
            VkSurfaceFormatKHR surfaceFormat,
            VkExtent2D        surfaceSize);

    /**
     * \brief Adds a HUD item if enabled
     *
     * \tparam T The HUD item type
     * \param [in] name HUD item name
     * \param [in] args Constructor arguments
     */
    template<typename T, typename... Args>
    void addItem(const char* name, int32_t at, Args... args) {
      m_hudItems.add<T>(name, at, std::forward<Args>(args)...);
    }

    /**
     * \brief Creates the HUD
     *
     * Creates and initializes the HUD if the
     * \c DXVK_HUD environment variable is set.
     * \param [in] device The DXVK device
     * \returns HUD object, if it was created.
     */
    static Rc<Hud> createHud(
      const Rc<DxvkDevice>& device);

  private:

    const Rc<DxvkDevice>  m_device;

    DxvkRasterizerState   m_rsState;
    DxvkBlendMode         m_blendMode;

    HudUniformData        m_uniformData;
    HudRenderer           m_renderer;
    HudItemSet            m_hudItems;

    float                 m_scale;

    void setupRendererState(
      const Rc<DxvkContext>&  ctx,
            VkSurfaceFormatKHR surfaceFormat,
            VkExtent2D        surfaceSize);

    void resetRendererState(
      const Rc<DxvkContext>&  ctx);

    void renderHudElements(
      const Rc<DxvkContext>&  ctx);

  };

}


code:
#pragma once

#include <cstdint>

namespace dxvk::hud {

  struct HudGlyph {
    uint32_t codePoint;
    int32_t  x;
    int32_t  y;
    int32_t  w;
    int32_t  h;
    int32_t  originX;
    int32_t  originY;
  };

  struct HudFont {
    int32_t  size;
    uint32_t width;
    uint32_t height;
    uint32_t falloff;
    uint32_t advance;
    uint32_t charCount;

    const HudGlyph* glyphs;
    const uint8_t* texture;
  };

  extern const HudFont g_hudFont;

}


code:
#include "dxvk_hud_item.h"

#include <iomanip>
#include <version.h>

namespace dxvk::hud {

  HudItem::~HudItem() {

  }

  void HudItem::update(dxvk::high_resolution_clock::time_point time) {
    // Do nothing by default. Some items won't need this.
  }

  HudItemSet::HudItemSet(const Rc<DxvkDevice>& device) {
    std::string configStr = env::getEnvVar("DXVK_HUD");

    if (configStr.empty())
      configStr = device->config().hud;

    std::string::size_type pos = 0;
    std::string::size_type end = 0;
    std::string::size_type mid = 0;

    while (pos < configStr.size()) {
      end = configStr.find(',', pos);
      mid = configStr.find('=', pos);

      if (end == std::string::npos)
        end = configStr.size();

      if (mid != std::string::npos && mid < end) {
        m_options.insert({
          configStr.substr(pos,     mid - pos),
          configStr.substr(mid + 1, end - mid - 1) });
      } else {
        m_enabled.insert(configStr.substr(pos, end - pos));
      }

      pos = end + 1;
    }

    if (m_enabled.find("full") != m_enabled.end())
      m_enableFull = true;

    if (m_enabled.find("1") != m_enabled.end()) {
      m_enabled.insert("devinfo");
      m_enabled.insert("fps");
    }
  }

  HudItemSet::~HudItemSet() {

  }

  void HudItemSet::update() {
    auto time = dxvk::high_resolution_clock::now();

    for (const auto& item : m_items)
      item->update(time);
  }

  void HudItemSet::render(HudRenderer& renderer) {
    HudPos position = { 8.0f, 8.0f };

    for (const auto& item : m_items)
      position = item->render(renderer, position);
  }

  void HudItemSet::parseOption(const std::string& str, float& value) {
    try {
      value = std::stof(str);
    } catch (const std::invalid_argument&) {
      return;
    }
  }

  HudPos HudVersionItem::render(
          HudRenderer&      renderer,
          HudPos            position) {
    position.y += 16.0f;

    renderer.drawText(16.0f,
      { position.x, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      "DXVK " DXVK_VERSION);

    position.y += 8.0f;
    return position;
  }

  HudClientApiItem::HudClientApiItem(std::string api)
  : m_api(api) {

  }

  HudClientApiItem::~HudClientApiItem() {

  }

  HudPos HudClientApiItem::render(
          HudRenderer&      renderer,
          HudPos            position) {
    position.y += 16.0f;

    renderer.drawText(16.0f,
      { position.x, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      m_api);

    position.y += 8.0f;
    return position;
  }

  HudDeviceInfoItem::HudDeviceInfoItem(const Rc<DxvkDevice>& device) {
    VkPhysicalDeviceProperties props = device->adapter()->deviceProperties();

    m_deviceName = props.deviceName;
    m_driverVer = str::format("Driver: ",
      VK_VERSION_MAJOR(props.driverVersion), ".",
      VK_VERSION_MINOR(props.driverVersion), ".",
      VK_VERSION_PATCH(props.driverVersion));
    m_vulkanVer = str::format("Vulkan: ",
      VK_VERSION_MAJOR(props.apiVersion), ".",
      VK_VERSION_MINOR(props.apiVersion), ".",
      VK_VERSION_PATCH(props.apiVersion));
  }

  HudDeviceInfoItem::~HudDeviceInfoItem() {

  }

  HudPos HudDeviceInfoItem::render(
          HudRenderer&      renderer,
          HudPos            position) {
    position.y += 16.0f;
    renderer.drawText(16.0f,
      { position.x, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      m_deviceName);

    position.y += 24.0f;
    renderer.drawText(16.0f,
      { position.x, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      m_driverVer);

    position.y += 20.0f;
    renderer.drawText(16.0f,
      { position.x, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      m_vulkanVer);

    position.y += 8.0f;
    return position;
  }

  HudFpsItem::HudFpsItem() { }
  HudFpsItem::~HudFpsItem() { }

  void HudFpsItem::update(dxvk::high_resolution_clock::time_point time) {
    m_frameCount += 1;

    auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(time - m_lastUpdate);

    if (elapsed.count() >= UpdateInterval) {
      int64_t fps = (10'000'000ll * m_frameCount) / elapsed.count();

      m_frameRate = str::format(fps / 10, ".", fps % 10);
      m_frameCount = 0;
      m_lastUpdate = time;
    }
  }

  HudPos HudFpsItem::render(
          HudRenderer&      renderer,
          HudPos            position) {
    position.y += 16.0f;

    renderer.drawText(16.0f,
      { position.x, position.y },
      { 1.0f, 0.25f, 0.25f, 1.0f },
      "FPS:");

    renderer.drawText(16.0f,
      { position.x + 60.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      m_frameRate);

    position.y += 8.0f;
    return position;
  }

  HudFrameTimeItem::HudFrameTimeItem() { }
  HudFrameTimeItem::~HudFrameTimeItem() { }

  void HudFrameTimeItem::update(dxvk::high_resolution_clock::time_point time) {
    auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(time - m_lastUpdate);

    m_dataPoints[m_dataPointId] = float(elapsed.count());
    m_dataPointId = (m_dataPointId + 1) % NumDataPoints;

    m_lastUpdate = time;
  }

  HudPos HudFrameTimeItem::render(
          HudRenderer&      renderer,
          HudPos            position) {
    std::array<HudGraphPoint, NumDataPoints> points;

    // 60 FPS = optimal, 10 FPS = worst
    const float targetUs =  16'666.6f;
    const float minUs    =   5'000.0f;
    const float maxUs    = 100'000.0f;

    // Ten times the maximum/minimum number
    // of milliseconds for a single frame
    uint32_t minMs = 0xFFFFFFFFu;
    uint32_t maxMs = 0x00000000u;

    // Paint the time points
    for (uint32_t i = 0; i < NumDataPoints; i++) {
      float us = m_dataPoints[(m_dataPointId + i) % NumDataPoints];

      minMs = std::min(minMs, uint32_t(us / 100.0f));
      maxMs = std::max(maxMs, uint32_t(us / 100.0f));

      float r = std::min(std::max(-1.0f + us / targetUs, 0.0f), 1.0f);
      float g = std::min(std::max( 3.0f - us / targetUs, 0.0f), 1.0f);
      float l = std::sqrt(r * r + g * g);

      HudNormColor color = {
        uint8_t(255.0f * (r / l)),
        uint8_t(255.0f * (g / l)),
        uint8_t(0), uint8_t(255) };

      float hVal = std::log2(std::max((us - minUs) / targetUs + 1.0f, 1.0f))
                 / std::log2((maxUs - minUs) / targetUs);

      points[i].value = std::max(hVal, 1.0f / 40.0f);
      points[i].color = color;
    }

    renderer.drawGraph(position,
      HudPos { float(NumDataPoints), 40.0f },
      points.size(), points.data());

    position.y += 58.0f;

    renderer.drawText(12.0f,
      { position.x, position.y },
      { 1.0f, 0.25f, 0.25f, 1.0f },
      "min:");

    renderer.drawText(12.0f,
      { position.x + 45.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      str::format(minMs / 10, ".", minMs % 10));

    renderer.drawText(12.0f,
      { position.x + 150.0f, position.y },
      { 1.0f, 0.25f, 0.25f, 1.0f },
      "max:");

    renderer.drawText(12.0f,
      { position.x + 195.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      str::format(maxMs / 10, ".", maxMs % 10));

    position.y += 4.0f;
    return position;
  }

  HudSubmissionStatsItem::HudSubmissionStatsItem(const Rc<DxvkDevice>& device)
  : m_device(device) {

  }

  HudSubmissionStatsItem::~HudSubmissionStatsItem() {

  }

  void HudSubmissionStatsItem::update(dxvk::high_resolution_clock::time_point time) {
    DxvkStatCounters counters = m_device->getStatCounters();

    uint64_t currSubmitCount = counters.getCtr(DxvkStatCounter::QueueSubmitCount);
    uint64_t currSyncCount = counters.getCtr(DxvkStatCounter::GpuSyncCount);
    uint64_t currSyncTicks = counters.getCtr(DxvkStatCounter::GpuSyncTicks);

    m_maxSubmitCount = std::max(m_maxSubmitCount, currSubmitCount - m_prevSubmitCount);
    m_maxSyncCount = std::max(m_maxSyncCount, currSyncCount - m_prevSyncCount);
    m_maxSyncTicks = std::max(m_maxSyncTicks, currSyncTicks - m_prevSyncTicks);

    m_prevSubmitCount = currSubmitCount;
    m_prevSyncCount = currSyncCount;
    m_prevSyncTicks = currSyncTicks;

    auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(time - m_lastUpdate);

    if (elapsed.count() >= UpdateInterval) {
      m_submitString = str::format(m_maxSubmitCount);

      uint64_t syncTicks = m_maxSyncTicks / 100;

      m_syncString = m_maxSyncCount
        ? str::format(m_maxSyncCount, " (", (syncTicks / 10), ".", (syncTicks % 10), " ms)")
        : str::format(m_maxSyncCount);

      m_maxSubmitCount = 0;
      m_maxSyncCount = 0;
      m_maxSyncTicks = 0;

      m_lastUpdate = time;
    }
  }

  HudPos HudSubmissionStatsItem::render(
          HudRenderer&      renderer,
          HudPos            position) {
    position.y += 16.0f;

    renderer.drawText(16.0f,
      { position.x, position.y },
      { 1.0f, 0.5f, 0.25f, 1.0f },
      "Queue submissions:");

    renderer.drawText(16.0f,
      { position.x + 228.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      m_submitString);

    position.y += 20.0f;
    renderer.drawText(16.0f,
      { position.x, position.y },
      { 1.0f, 0.5f, 0.25f, 1.0f },
      "Queue syncs:");

    renderer.drawText(16.0f,
      { position.x + 228.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      m_syncString);

    position.y += 8.0f;
    return position;
  }

  HudDrawCallStatsItem::HudDrawCallStatsItem(const Rc<DxvkDevice>& device)
  : m_device(device) {

  }

  HudDrawCallStatsItem::~HudDrawCallStatsItem() {

  }

  void HudDrawCallStatsItem::update(dxvk::high_resolution_clock::time_point time) {
    auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(time - m_lastUpdate);

    DxvkStatCounters counters = m_device->getStatCounters();
    auto diffCounters = counters.diff(m_prevCounters);

    if (elapsed.count() >= UpdateInterval) {
      m_gpCount = diffCounters.getCtr(DxvkStatCounter::CmdDrawCalls);
      m_cpCount = diffCounters.getCtr(DxvkStatCounter::CmdDispatchCalls);
      m_rpCount = diffCounters.getCtr(DxvkStatCounter::CmdRenderPassCount);
      m_pbCount = diffCounters.getCtr(DxvkStatCounter::CmdBarrierCount);

      m_lastUpdate = time;
    }

    m_prevCounters = counters;
  }

  HudPos HudDrawCallStatsItem::render(
          HudRenderer&      renderer,
          HudPos            position) {
    position.y += 16.0f;
    renderer.drawText(16.0f,
      { position.x, position.y },
      { 0.25f, 0.5f, 1.0f, 1.0f },
      "Draw calls:");

    renderer.drawText(16.0f,
      { position.x + 192.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      str::format(m_gpCount));

    position.y += 20.0f;
    renderer.drawText(16.0f,
      { position.x, position.y },
      { 0.25f, 0.5f, 1.0f, 1.0f },
      "Dispatch calls:");

    renderer.drawText(16.0f,
      { position.x + 192.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      str::format(m_cpCount));

    position.y += 20.0f;
    renderer.drawText(16.0f,
      { position.x, position.y },
      { 0.25f, 0.5f, 1.0f, 1.0f },
      "Render passes:");

    renderer.drawText(16.0f,
      { position.x + 192.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      str::format(m_rpCount));

    position.y += 20.0f;
    renderer.drawText(16.0f,
      { position.x, position.y },
      { 0.25f, 0.5f, 1.0f, 1.0f },
      "Barriers:");

    renderer.drawText(16.0f,
      { position.x + 192.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      str::format(m_pbCount));

    position.y += 8.0f;
    return position;
  }

  HudPipelineStatsItem::HudPipelineStatsItem(const Rc<DxvkDevice>& device)
  : m_device(device) {

  }

  HudPipelineStatsItem::~HudPipelineStatsItem() {

  }

  void HudPipelineStatsItem::update(dxvk::high_resolution_clock::time_point time) {
    DxvkStatCounters counters = m_device->getStatCounters();

    m_graphicsPipelines = counters.getCtr(DxvkStatCounter::PipeCountGraphics);
    m_computePipelines  = counters.getCtr(DxvkStatCounter::PipeCountCompute);
  }

  HudPos HudPipelineStatsItem::render(
          HudRenderer&      renderer,
          HudPos            position) {
    position.y += 16.0f;
    renderer.drawText(16.0f,
      { position.x, position.y },
      { 1.0f, 0.25f, 1.0f, 1.0f },
      "Graphics pipelines:");

    renderer.drawText(16.0f,
      { position.x + 240.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      str::format(m_graphicsPipelines));

    position.y += 20.0f;
    renderer.drawText(16.0f,
      { position.x, position.y },
      { 1.0f, 0.25f, 1.0f, 1.0f },
      "Compute pipelines:");

    renderer.drawText(16.0f,
      { position.x + 240.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      str::format(m_computePipelines));

    position.y += 8.0f;
    return position;
  }

  HudMemoryStatsItem::HudMemoryStatsItem(const Rc<DxvkDevice>& device)
  : m_device(device), m_memory(device->adapter()->memoryProperties()) {

  }

  HudMemoryStatsItem::~HudMemoryStatsItem() {

  }

  void HudMemoryStatsItem::update(dxvk::high_resolution_clock::time_point time) {
    for (uint32_t i = 0; i < m_memory.memoryHeapCount; i++)
      m_heaps[i] = m_device->getMemoryStats(i);
  }

  HudPos HudMemoryStatsItem::render(
          HudRenderer&      renderer,
          HudPos            position) {
    for (uint32_t i = 0; i < m_memory.memoryHeapCount; i++) {
      bool isDeviceLocal = m_memory.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT;

      uint64_t memUsedMib = m_heaps[i].memoryUsed >> 20;
      uint64_t memAllocatedMib = m_heaps[i].memoryAllocated >> 20;
      uint64_t percentage = (100 * m_heaps[i].memoryAllocated) / m_memory.memoryHeaps[i].size;

      std::string label = str::format(isDeviceLocal ? "Vidmem" : "Sysmem", " heap ", i, ": ");
      std::string text  = str::format(std::setfill(' '), std::setw(5), memAllocatedMib, " MB (", percentage, "%) ",
        std::setw(5 + (percentage < 10 ? 1 : 0) + (percentage < 100 ? 1 : 0)), memUsedMib, " MB used");

      position.y += 16.0f;
      renderer.drawText(16.0f,
        { position.x, position.y },
        { 1.0f, 1.0f, 0.25f, 1.0f },
        label);

      renderer.drawText(16.0f,
        { position.x + 168.0f, position.y },
        { 1.0f, 1.0f, 1.0f, 1.0f },
        text);
      position.y += 4.0f;
    }

    position.y += 4.0f;
    return position;
  }

  HudCsThreadItem::HudCsThreadItem(const Rc<DxvkDevice>& device)
  : m_device(device) {

  }

  HudCsThreadItem::~HudCsThreadItem() {

  }

  void HudCsThreadItem::update(dxvk::high_resolution_clock::time_point time) {
    uint64_t ticks = std::chrono::duration_cast<std::chrono::microseconds>(time - m_lastUpdate).count();

    // Capture the maximum here since it's more useful to
    // identify stutters than using any sort of average
    DxvkStatCounters counters = m_device->getStatCounters();
    uint64_t currCsSyncCount = counters.getCtr(DxvkStatCounter::CsSyncCount);
    uint64_t currCsSyncTicks = counters.getCtr(DxvkStatCounter::CsSyncTicks);

    m_maxCsSyncCount = std::max(m_maxCsSyncCount, currCsSyncCount - m_prevCsSyncCount);
    m_maxCsSyncTicks = std::max(m_maxCsSyncTicks, currCsSyncTicks - m_prevCsSyncTicks);

    m_prevCsSyncCount = currCsSyncCount;
    m_prevCsSyncTicks = currCsSyncTicks;

    m_updateCount++;

    if (ticks >= UpdateInterval) {
      uint64_t currCsChunks = counters.getCtr(DxvkStatCounter::CsChunkCount);
      uint64_t diffCsChunks = (currCsChunks - m_prevCsChunks) / m_updateCount;
      m_prevCsChunks = currCsChunks;

      uint64_t syncTicks = m_maxCsSyncTicks / 100;

      m_csChunkString = str::format(diffCsChunks);
      m_csSyncString = m_maxCsSyncCount
        ? str::format(m_maxCsSyncCount, " (", (syncTicks / 10), ".", (syncTicks % 10), " ms)")
        : str::format(m_maxCsSyncCount);

      m_maxCsSyncCount = 0;
      m_maxCsSyncTicks = 0;

      m_updateCount = 0;
      m_lastUpdate = time;
    }
  }

  HudPos HudCsThreadItem::render(
          HudRenderer&      renderer,
          HudPos            position) {
    position.y += 16.0f;
    renderer.drawText(16.0f,
      { position.x, position.y },
      { 0.25f, 1.0f, 0.25f, 1.0f },
      "CS chunks:");

    renderer.drawText(16.0f,
      { position.x + 132.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      m_csChunkString);

    position.y += 20.0f;
    renderer.drawText(16.0f,
      { position.x, position.y },
      { 0.25f, 1.0f, 0.25f, 1.0f },
      "CS syncs:");

    renderer.drawText(16.0f,
      { position.x + 132.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      m_csSyncString);

    position.y += 8.0f;
    return position;
  }

  HudGpuLoadItem::HudGpuLoadItem(const Rc<DxvkDevice>& device)
  : m_device(device) {

  }

  HudGpuLoadItem::~HudGpuLoadItem() {

  }

  void HudGpuLoadItem::update(dxvk::high_resolution_clock::time_point time) {
    uint64_t ticks = std::chrono::duration_cast<std::chrono::microseconds>(time - m_lastUpdate).count();

    if (ticks >= UpdateInterval) {
      DxvkStatCounters counters = m_device->getStatCounters();
      uint64_t currGpuIdleTicks = counters.getCtr(DxvkStatCounter::GpuIdleTicks);

      m_diffGpuIdleTicks = currGpuIdleTicks - m_prevGpuIdleTicks;
      m_prevGpuIdleTicks = currGpuIdleTicks;

      uint64_t busyTicks = ticks > m_diffGpuIdleTicks
        ? uint64_t(ticks - m_diffGpuIdleTicks)
        : uint64_t(0);

      m_gpuLoadString = str::format((100 * busyTicks) / ticks, "%");
      m_lastUpdate = time;
    }
  }

  HudPos HudGpuLoadItem::render(
          HudRenderer&      renderer,
          HudPos            position) {
    position.y += 16.0f;

    renderer.drawText(16.0f,
      { position.x, position.y },
      { 0.25f, 0.5f, 0.25f, 1.0f },
      "GPU:");

    renderer.drawText(16.0f,
      { position.x + 60.0f, position.y },
      { 1.0f, 1.0f, 1.0f, 1.0f },
      m_gpuLoadString);

    position.y += 8.0f;
    return position;
  }

  HudCompilerActivityItem::HudCompilerActivityItem(const Rc<DxvkDevice>& device)
  : m_device(device) {

  }

  HudCompilerActivityItem::~HudCompilerActivityItem() {

  }

  void HudCompilerActivityItem::update(dxvk::high_resolution_clock::time_point time) {
    DxvkStatCounters counters = m_device->getStatCounters();
    bool doShow = counters.getCtr(DxvkStatCounter::PipeCompilerBusy);

    if (!doShow) {
      auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(time - m_timeShown);
      doShow = elapsed.count() <= MinShowDuration;
    }

    if (doShow && !m_show)
      m_timeShown = time;

    m_show = doShow;
  }

  HudPos HudCompilerActivityItem::render(
          HudRenderer&      renderer,
          HudPos            position) {
    if (m_show) {
      renderer.drawText(16.0f,
        { position.x, renderer.surfaceSize().height / renderer.scale() - 20.0f },
        { 1.0f, 1.0f, 1.0f, 1.0f },
        "Compiling shaders...");
    }

    return position;
  }

}


code:
#pragma once

#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "../../util/util_time.h"

#include "dxvk_hud_renderer.h"

namespace dxvk::hud {

  /**
   * \brief HUD item
   *
   * A single named item in the HUD that
   * can be enabled by the user.
   */
  class HudItem : public RcObject {

  public:

    virtual ~HudItem();

    /**
     * \brief Updates the HUD item
     * \param [in] time Current time
     */
    virtual void update(
            dxvk::high_resolution_clock::time_point time);

    /**
     * \brief Renders the HUD
     *
     * \param [in] renderer HUD renderer
     * \param [in] position Base offset
     * \returns Base offset for next item
     */
    virtual HudPos render(
            HudRenderer&      renderer,
            HudPos            position) = 0;

  };

  /**
   * \brief HUD item set
   *
   * Manages HUD items.
   */
  class HudItemSet {

  public:

    HudItemSet(const Rc<DxvkDevice>& device);

    ~HudItemSet();

    /**
     * \brief Updates the HUD
     * Updates all enabled HUD items.
     */
    void update();

    /**
     * \brief Renders the HUD
     *
     * \param [in] renderer HUD renderer
     * \returns Base offset for next item
     */
    void render(
            HudRenderer&      renderer);

    /**
     * \brief Creates a HUD item if enabled
     *
     * \tparam T The HUD item type
     * \param [in] name HUD item name
     * \param [in] at Position at which to insert the item
     * \param [in] args Constructor arguments
     */
    template<typename T, typename... Args>
    void add(const char* name, int32_t at, Args... args) {
      bool enable = m_enableFull;

      if (!enable) {
        auto entry = m_enabled.find(name);
        enable = entry != m_enabled.end();
      }

      if (at < 0 || at > int32_t(m_items.size()))
        at = m_items.size();

      if (enable) {
        m_items.insert(m_items.begin() + at,
          new T(std::forward<Args>(args)...));
      }
    }

    template<typename T>
    T getOption(const char *option, T fallback) {
      auto entry = m_options.find(option);
      if (entry == m_options.end())
        return fallback;

      T value = fallback;
      parseOption(entry->second, value);
      return value;
    }

  private:

    bool                                          m_enableFull = false;
    std::unordered_set<std::string>               m_enabled;
    std::unordered_map<std::string, std::string>  m_options;
    std::vector<Rc<HudItem>>                      m_items;

    static void parseOption(const std::string& str, float& value);

  };

  /**
   * \brief HUD item to display DXVK version
   */
  class HudVersionItem : public HudItem {

  public:

    HudPos render(
            HudRenderer&      renderer,
            HudPos            position);

  };

  /**
   * \brief HUD item to display the client API
   */
  class HudClientApiItem : public HudItem {

  public:

    HudClientApiItem(std::string api);

    ~HudClientApiItem();

    HudPos render(
            HudRenderer&      renderer,
            HudPos            position);

  private:

    std::string m_api;

  };

  /**
   * \brief HUD item to display device info
   */
  class HudDeviceInfoItem : public HudItem {

  public:

    HudDeviceInfoItem(const Rc<DxvkDevice>& device);

    ~HudDeviceInfoItem();

    HudPos render(
            HudRenderer&      renderer,
            HudPos            position);

  private:

    std::string m_deviceName;
    std::string m_driverVer;
    std::string m_vulkanVer;

  };

  /**
   * \brief HUD item to display the frame rate
   */
  class HudFpsItem : public HudItem {
    constexpr static int64_t UpdateInterval = 500'000;
  public:

    HudFpsItem();

    ~HudFpsItem();

    void update(dxvk::high_resolution_clock::time_point time);

    HudPos render(
            HudRenderer&      renderer,
            HudPos            position);

  private:

    uint32_t                                m_frameCount = 0;
    dxvk::high_resolution_clock::time_point m_lastUpdate
      = dxvk::high_resolution_clock::now();

    std::string m_frameRate;

  };

  /**
   * \brief HUD item to display the frame rate
   */
  class HudFrameTimeItem : public HudItem {
    constexpr static size_t NumDataPoints = 304;
  public:

    HudFrameTimeItem();

    ~HudFrameTimeItem();

    void update(dxvk::high_resolution_clock::time_point time);

    HudPos render(
            HudRenderer&      renderer,
            HudPos            position);

  private:

    dxvk::high_resolution_clock::time_point m_lastUpdate
      = dxvk::high_resolution_clock::now();

    std::array<float, NumDataPoints>  m_dataPoints  = {};
    uint32_t                          m_dataPointId = 0;

  };

  /**
   * \brief HUD item to display queue statistics
   */
  class HudSubmissionStatsItem : public HudItem {
    constexpr static int64_t UpdateInterval = 500'000;
  public:

    HudSubmissionStatsItem(const Rc<DxvkDevice>& device);

    ~HudSubmissionStatsItem();

    void update(dxvk::high_resolution_clock::time_point time);

    HudPos render(
            HudRenderer&      renderer,
            HudPos            position);

  private:

    Rc<DxvkDevice>  m_device;

    uint64_t        m_prevSubmitCount = 0;
    uint64_t        m_prevSyncCount   = 0;
    uint64_t        m_prevSyncTicks   = 0;

    uint64_t        m_maxSubmitCount  = 0;
    uint64_t        m_maxSyncCount    = 0;
    uint64_t        m_maxSyncTicks    = 0;

    std::string     m_submitString;
    std::string     m_syncString;

    dxvk::high_resolution_clock::time_point m_lastUpdate
      = dxvk::high_resolution_clock::now();

  };

  /**
   * \brief HUD item to display draw call counts
   */
  class HudDrawCallStatsItem : public HudItem {
    constexpr static int64_t UpdateInterval = 500'000;
  public:

    HudDrawCallStatsItem(const Rc<DxvkDevice>& device);

    ~HudDrawCallStatsItem();

    void update(dxvk::high_resolution_clock::time_point time);

    HudPos render(
            HudRenderer&      renderer,
            HudPos            position);

  private:

    Rc<DxvkDevice>    m_device;

    DxvkStatCounters  m_prevCounters;

    uint64_t          m_gpCount = 0;
    uint64_t          m_cpCount = 0;
    uint64_t          m_rpCount = 0;
    uint64_t          m_pbCount = 0;

    dxvk::high_resolution_clock::time_point m_lastUpdate
      = dxvk::high_resolution_clock::now();

  };

  /**
   * \brief HUD item to display pipeline counts
   */
  class HudPipelineStatsItem : public HudItem {

  public:

    HudPipelineStatsItem(const Rc<DxvkDevice>& device);

    ~HudPipelineStatsItem();

    void update(dxvk::high_resolution_clock::time_point time);

    HudPos render(
            HudRenderer&      renderer,
            HudPos            position);

  private:

    Rc<DxvkDevice> m_device;

    uint64_t m_graphicsPipelines = 0;
    uint64_t m_computePipelines = 0;

  };

  /**
   * \brief HUD item to display memory usage
   */
  class HudMemoryStatsItem : public HudItem {

  public:

    HudMemoryStatsItem(const Rc<DxvkDevice>& device);

    ~HudMemoryStatsItem();

    void update(dxvk::high_resolution_clock::time_point time);

    HudPos render(
            HudRenderer&      renderer,
            HudPos            position);

  private:

    Rc<DxvkDevice>                    m_device;
    VkPhysicalDeviceMemoryProperties  m_memory;
    DxvkMemoryStats                   m_heaps[VK_MAX_MEMORY_HEAPS];

  };

  /**
   * \brief HUD item to display CS thread statistics
   */
  class HudCsThreadItem : public HudItem {
    constexpr static int64_t UpdateInterval = 500'000;
  public:

    HudCsThreadItem(const Rc<DxvkDevice>& device);

    ~HudCsThreadItem();

    void update(dxvk::high_resolution_clock::time_point time);

    HudPos render(
            HudRenderer&      renderer,
            HudPos            position);

  private:

    Rc<DxvkDevice> m_device;

    uint64_t m_prevCsSyncCount  = 0;
    uint64_t m_prevCsSyncTicks  = 0;
    uint64_t m_prevCsChunks     = 0;

    uint64_t m_maxCsSyncCount   = 0;
    uint64_t m_maxCsSyncTicks   = 0;

    uint64_t m_updateCount      = 0;

    std::string m_csSyncString;
    std::string m_csChunkString;

    dxvk::high_resolution_clock::time_point m_lastUpdate
      = dxvk::high_resolution_clock::now();

  };

  /**
   * \brief HUD item to display GPU load
   */
  class HudGpuLoadItem : public HudItem {
    constexpr static int64_t UpdateInterval = 500'000;
  public:

    HudGpuLoadItem(const Rc<DxvkDevice>& device);

    ~HudGpuLoadItem();

    void update(dxvk::high_resolution_clock::time_point time);

    HudPos render(
            HudRenderer&      renderer,
            HudPos            position);

  private:

    Rc<DxvkDevice> m_device;

    uint64_t m_prevGpuIdleTicks = 0;
    uint64_t m_diffGpuIdleTicks = 0;

    std::string m_gpuLoadString;

    dxvk::high_resolution_clock::time_point m_lastUpdate
      = dxvk::high_resolution_clock::now();

  };

  /**
   * \brief HUD item to display pipeline compiler activity
   */
  class HudCompilerActivityItem : public HudItem {
    constexpr static int64_t MinShowDuration = 1500;
  public:

    HudCompilerActivityItem(const Rc<DxvkDevice>& device);

    ~HudCompilerActivityItem();

    void update(dxvk::high_resolution_clock::time_point time);

    HudPos render(
            HudRenderer&      renderer,
            HudPos            position);

  private:

    Rc<DxvkDevice> m_device;

    bool m_show = false;

    dxvk::high_resolution_clock::time_point m_timeShown
      = dxvk::high_resolution_clock::now();

  };

}


code:
#include "dxvk_hud_renderer.h"

#include <hud_graph_frag.h>
#include <hud_graph_vert.h>

#include <hud_text_frag.h>
#include <hud_text_vert.h>

namespace dxvk::hud {

  HudRenderer::HudRenderer(const Rc<DxvkDevice>& device)
  : m_mode          (Mode::RenderNone),
    m_scale         (1.0f),
    m_surfaceSize   { 0, 0 },
    m_device        (device),
    m_textShaders   (createTextShaders()),
    m_graphShaders  (createGraphShaders()),
    m_dataBuffer    (createDataBuffer()),
    m_dataView      (createDataView()),
    m_dataOffset    (0ull),
    m_fontBuffer    (createFontBuffer()),
    m_fontImage     (createFontImage()),
    m_fontView      (createFontView()),
    m_fontSampler   (createFontSampler()) {

  }

  HudRenderer::~HudRenderer() {

  }

  void HudRenderer::beginFrame(const Rc<DxvkContext>& context, VkExtent2D surfaceSize, float scale) {
    if (!m_initialized)
      this->initFontTexture(context);

    m_mode        = Mode::RenderNone;
    m_scale       = scale;
    m_surfaceSize = surfaceSize;
    m_context     = context;
  }

  void HudRenderer::drawText(
          float             size,
          HudPos            pos,
          HudColor          color,
    const std::string&      text) {
    if (text.empty())
      return;

    beginTextRendering();

    // Copy string into string buffer, but extend it to cover a full cache
    // line to avoid potential CPU performance issues with the upload.
    std::string textCopy = text;
    textCopy.resize(align(text.size(), CACHE_LINE_SIZE), ' ');

    VkDeviceSize offset = allocDataBuffer(textCopy.size());
    std::memcpy(m_dataBuffer->mapPtr(offset), textCopy.data(), textCopy.size());

    // Fill in push constants for the next draw
    HudTextPushConstants pushData;
    pushData.color = color;
    pushData.pos = pos;
    pushData.offset = offset;
    pushData.size = size;
    pushData.scale.x = m_scale / std::max(float(m_surfaceSize.width),  1.0f);
    pushData.scale.y = m_scale / std::max(float(m_surfaceSize.height), 1.0f);

    m_context->pushConstants(0, sizeof(pushData), &pushData);

    // Draw with orignal vertex count
    m_context->draw(6 * text.size(), 1, 0, 0);
  }

  void HudRenderer::drawGraph(
          HudPos            pos,
          HudPos            size,
          size_t            pointCount,
    const HudGraphPoint*    pointData) {
    beginGraphRendering();

    VkDeviceSize dataSize = pointCount * sizeof(*pointData);
    VkDeviceSize offset = allocDataBuffer(dataSize);
    std::memcpy(m_dataBuffer->mapPtr(offset), pointData, dataSize);

    HudGraphPushConstants pushData;
    pushData.offset = offset / sizeof(*pointData);
    pushData.count = pointCount;
    pushData.pos = pos;
    pushData.size = size;
    pushData.scale.x = m_scale / std::max(float(m_surfaceSize.width),  1.0f);
    pushData.scale.y = m_scale / std::max(float(m_surfaceSize.height), 1.0f);

    m_context->pushConstants(0, sizeof(pushData), &pushData);
    m_context->draw(4, 1, 0, 0);
  }

  void HudRenderer::beginTextRendering() {
    if (m_mode != Mode::RenderText) {
      m_mode = Mode::RenderText;

      m_context->bindShader(VK_SHADER_STAGE_VERTEX_BIT,   m_textShaders.vert);
      m_context->bindShader(VK_SHADER_STAGE_FRAGMENT_BIT, m_textShaders.frag);

      m_context->bindResourceBuffer (0, DxvkBufferSlice(m_fontBuffer));
      m_context->bindResourceView   (1, nullptr, m_dataView);
      m_context->bindResourceSampler(2, m_fontSampler);
      m_context->bindResourceView   (2, m_fontView, nullptr);

      static const DxvkInputAssemblyState iaState = {
        VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
        VK_FALSE, 0 };

      m_context->setInputAssemblyState(iaState);
      m_context->setInputLayout(0, nullptr, 0, nullptr);
    }
  }

  void HudRenderer::beginGraphRendering() {
    if (m_mode != Mode::RenderGraph) {
      m_mode = Mode::RenderGraph;

      m_context->bindShader(VK_SHADER_STAGE_VERTEX_BIT,   m_graphShaders.vert);
      m_context->bindShader(VK_SHADER_STAGE_FRAGMENT_BIT, m_graphShaders.frag);

      m_context->bindResourceBuffer(0, DxvkBufferSlice(m_dataBuffer));

      static const DxvkInputAssemblyState iaState = {
        VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
        VK_FALSE, 0 };

      m_context->setInputAssemblyState(iaState);
      m_context->setInputLayout(0, nullptr, 0, nullptr);
    }
  }

  VkDeviceSize HudRenderer::allocDataBuffer(VkDeviceSize size) {
    if (m_dataOffset + size > m_dataBuffer->info().size) {
      m_context->invalidateBuffer(m_dataBuffer, m_dataBuffer->allocSlice());
      m_dataOffset = 0;
    }

    VkDeviceSize offset = m_dataOffset;
    m_dataOffset = align(offset + size, 64);
    return offset;
  }

  HudRenderer::ShaderPair HudRenderer::createTextShaders() {
    ShaderPair result;

    SpirvCodeBuffer vsCode(hud_text_vert);
    SpirvCodeBuffer fsCode(hud_text_frag);

    const std::array<DxvkResourceSlot, 2> vsResources = {{
      { 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,       VK_IMAGE_VIEW_TYPE_MAX_ENUM },
      { 1, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, VK_IMAGE_VIEW_TYPE_MAX_ENUM },
    }};

    const std::array<DxvkResourceSlot, 1> fsResources = {{
      { 2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_IMAGE_VIEW_TYPE_2D },
    }};

    DxvkShaderCreateInfo vsInfo;
    vsInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
    vsInfo.resourceSlotCount = vsResources.size();
    vsInfo.resourceSlots = vsResources.data();
    vsInfo.outputMask = 0x3;
    vsInfo.pushConstSize = sizeof(HudTextPushConstants);
    result.vert = new DxvkShader(vsInfo, std::move(vsCode));

    DxvkShaderCreateInfo fsInfo;
    fsInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
    fsInfo.resourceSlotCount = fsResources.size();
    fsInfo.resourceSlots = fsResources.data();
    fsInfo.inputMask = 0x3;
    fsInfo.outputMask = 0x1;
    result.frag = new DxvkShader(fsInfo, std::move(fsCode));

    return result;
  }

  HudRenderer::ShaderPair HudRenderer::createGraphShaders() {
    ShaderPair result;

    SpirvCodeBuffer vsCode(hud_graph_vert);
    SpirvCodeBuffer fsCode(hud_graph_frag);

    const std::array<DxvkResourceSlot, 1> fsResources = {{
      { 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_IMAGE_VIEW_TYPE_MAX_ENUM },
    }};

    DxvkShaderCreateInfo vsInfo;
    vsInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
    vsInfo.outputMask = 0x1;
    vsInfo.pushConstSize = sizeof(HudGraphPushConstants);
    result.vert = new DxvkShader(vsInfo, std::move(vsCode));

    DxvkShaderCreateInfo fsInfo;
    fsInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
    fsInfo.resourceSlotCount = fsResources.size();
    fsInfo.resourceSlots = fsResources.data();
    fsInfo.inputMask = 0x1;
    fsInfo.outputMask = 0x1;
    fsInfo.pushConstSize = sizeof(HudGraphPushConstants);
    result.frag = new DxvkShader(fsInfo, std::move(fsCode));

    return result;
  }

  Rc<DxvkBuffer> HudRenderer::createDataBuffer() {
    DxvkBufferCreateInfo info;
    info.size           = DataBufferSize;
    info.usage          = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
                        | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT;
    info.stages         = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT
                        | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
    info.access         = VK_ACCESS_SHADER_READ_BIT;

    return m_device->createBuffer(info,
      VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
      VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
      VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
  }

  Rc<DxvkBufferView> HudRenderer::createDataView() {
    DxvkBufferViewCreateInfo info;
    info.format = VK_FORMAT_R8_UINT;
    info.rangeOffset = 0;
    info.rangeLength = m_dataBuffer->info().size;

    return m_device->createBufferView(m_dataBuffer, info);
  }

  Rc<DxvkBuffer> HudRenderer::createFontBuffer() {
    DxvkBufferCreateInfo info;
    info.size           = sizeof(HudFontGpuData);
    info.usage          = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
                        | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
    info.stages         = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT
                        | VK_PIPELINE_STAGE_TRANSFER_BIT;
    info.access         = VK_ACCESS_SHADER_READ_BIT
                        | VK_ACCESS_TRANSFER_WRITE_BIT;

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

  Rc<DxvkImage> HudRenderer::createFontImage() {
    DxvkImageCreateInfo info;
    info.type           = VK_IMAGE_TYPE_2D;
    info.format         = VK_FORMAT_R8_UNORM;
    info.flags          = 0;
    info.sampleCount    = VK_SAMPLE_COUNT_1_BIT;
    info.extent         = { g_hudFont.width, g_hudFont.height, 1 };
    info.numLayers      = 1;
    info.mipLevels      = 1;
    info.usage          = VK_IMAGE_USAGE_TRANSFER_DST_BIT
                        | VK_IMAGE_USAGE_SAMPLED_BIT;
    info.stages         = VK_PIPELINE_STAGE_TRANSFER_BIT
                        | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
    info.access         = VK_ACCESS_TRANSFER_WRITE_BIT
                        | VK_ACCESS_SHADER_READ_BIT;
    info.tiling         = VK_IMAGE_TILING_OPTIMAL;
    info.layout         = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;

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

  Rc<DxvkImageView> HudRenderer::createFontView() {
    DxvkImageViewCreateInfo info;
    info.type           = VK_IMAGE_VIEW_TYPE_2D;
    info.format         = m_fontImage->info().format;
    info.usage          = VK_IMAGE_USAGE_SAMPLED_BIT;
    info.aspect         = VK_IMAGE_ASPECT_COLOR_BIT;
    info.minLevel       = 0;
    info.numLevels      = 1;
    info.minLayer       = 0;
    info.numLayers      = 1;

    return m_device->createImageView(m_fontImage, info);
  }

  Rc<DxvkSampler> HudRenderer::createFontSampler() {
    DxvkSamplerCreateInfo info;
    info.magFilter      = VK_FILTER_LINEAR;
    info.minFilter      = VK_FILTER_LINEAR;
    info.mipmapMode     = VK_SAMPLER_MIPMAP_MODE_NEAREST;
    info.mipmapLodBias  = 0.0f;
    info.mipmapLodMin   = 0.0f;
    info.mipmapLodMax   = 0.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_TRUE;
    info.nonSeamless    = VK_FALSE;

    return m_device->createSampler(info);
  }

  void HudRenderer::initFontTexture(
    const Rc<DxvkContext>& context) {
    HudFontGpuData gpuData = { };
    gpuData.size    = float(g_hudFont.size);
    gpuData.advance = float(g_hudFont.advance);

    for (uint32_t i = 0; i < g_hudFont.charCount; i++) {
      auto src = &g_hudFont.glyphs[i];
      auto dst = &gpuData.glyphs[src->codePoint];

      dst->x = src->x;
      dst->y = src->y;
      dst->w = src->w;
      dst->h = src->h;
      dst->originX = src->originX;
      dst->originY = src->originY;
    }

    context->uploadBuffer(m_fontBuffer, &gpuData);

    context->uploadImage(m_fontImage,
      VkImageSubresourceLayers { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 },
      g_hudFont.texture, g_hudFont.width, g_hudFont.width * g_hudFont.height);

    m_initialized = true;
  }

}


code:
#pragma once

#include "../dxvk_device.h"

#include "dxvk_hud_font.h"

namespace dxvk::hud {

  /**
   * \brief HUD coordinates
   *
   * Coordinates relative to the top-left
   * corner of the swap image, in pixels.
   */
  struct HudPos {
    float x;
    float y;
  };

  /**
   * \brief Color
   *
   * SRGB color with alpha channel. The text
   * will use this color for the most part.
   */
  struct HudColor {
    float r;
    float g;
    float b;
    float a;
  };

  /**
   * \brief Normalized color
   *
   * SRGB color with alpha channel.
   */
  struct HudNormColor {
    uint8_t a;
    uint8_t b;
    uint8_t g;
    uint8_t r;
  };

  /**
   * \brief Graph point with color
   */
  struct HudGraphPoint {
    float         value;
    HudNormColor  color;
  };

  /**
   * \brief HUD push constant data
   */
  struct HudTextPushConstants {
    HudColor color;
    HudPos pos;
    uint32_t offset;
    float size;
    HudPos scale;
  };

  struct HudGraphPushConstants {
    uint32_t offset;
    uint32_t count;
    HudPos pos;
    HudPos size;
    HudPos scale;
  };

  /**
   * \brief Glyph data
   */
  struct HudGlyphGpuData {
    int16_t x;
    int16_t y;
    int16_t w;
    int16_t h;
    int16_t originX;
    int16_t originY;
  };

  struct HudFontGpuData {
    float size;
    float advance;
    uint32_t padding[2];
    HudGlyphGpuData glyphs[256];
  };

  /**
   * \brief Text renderer for the HUD
   *
   * Can be used by the presentation backend to
   * display performance and driver information.
   */
  class HudRenderer {
    constexpr static VkDeviceSize DataBufferSize = 16384;
  public:

    HudRenderer(
      const Rc<DxvkDevice>&   device);

    ~HudRenderer();

    void beginFrame(
      const Rc<DxvkContext>&  context,
            VkExtent2D        surfaceSize,
            float             scale);

    void drawText(
            float             size,
            HudPos            pos,
            HudColor          color,
      const std::string&      text);

    void drawGraph(
            HudPos            pos,
            HudPos            size,
            size_t            pointCount,
      const HudGraphPoint*    pointData);

    VkExtent2D surfaceSize() const {
      return m_surfaceSize;
    }

    float scale() const {
      return m_scale;
    }

  private:

    enum class Mode {
      RenderNone,
      RenderText,
      RenderGraph,
    };

    struct ShaderPair {
      Rc<DxvkShader> vert;
      Rc<DxvkShader> frag;
    };

    Mode                m_mode;
    float               m_scale;
    VkExtent2D          m_surfaceSize;

    Rc<DxvkDevice>      m_device;
    Rc<DxvkContext>     m_context;

    ShaderPair          m_textShaders;
    ShaderPair          m_graphShaders;

    Rc<DxvkBuffer>      m_dataBuffer;
    Rc<DxvkBufferView>  m_dataView;
    VkDeviceSize        m_dataOffset;

    Rc<DxvkBuffer>      m_fontBuffer;
    Rc<DxvkImage>       m_fontImage;
    Rc<DxvkImageView>   m_fontView;
    Rc<DxvkSampler>     m_fontSampler;

    bool                m_initialized = false;

    void beginTextRendering();

    void beginGraphRendering();

    VkDeviceSize allocDataBuffer(VkDeviceSize size);

    ShaderPair createTextShaders();
    ShaderPair createGraphShaders();

    Rc<DxvkBuffer> createDataBuffer();
    Rc<DxvkBufferView> createDataView();

    Rc<DxvkBuffer> createFontBuffer();
    Rc<DxvkImage> createFontImage();
    Rc<DxvkImageView> createFontView();
    Rc<DxvkSampler> createFontSampler();

    void initFontTexture(
      const Rc<DxvkContext>& context);

  };

}


code:
#version 450

layout(constant_id = 1225) const bool srgbSwapchain = false;

layout(location = 0) in  vec2 v_coord;
layout(location = 0) out vec4 o_color;

struct line_point_t {
  float value;
  uint  color;
};

layout(binding = 0, std430)
readonly buffer line_data_t {
  line_point_t points[];
};

layout(push_constant)
uniform push_data_t {
  uint offset;
  uint count;
  vec2 pos;
  vec2 size;
  vec2 scale;
};

vec3 linearToSrgb(vec3 color) {
  bvec3 isLo = lessThanEqual(color, vec3(0.0031308f));

  vec3 loPart = color * 12.92f;
  vec3 hiPart = pow(color, vec3(5.0f / 12.0f)) * 1.055f - 0.055f;
  return mix(hiPart, loPart, isLo);
}

void main() {
  float cx = v_coord.x * float(count);
  float fx = fract(cx);

  // We need to roll our own linear interpolation here
  uint p0_idx = min(uint(floor(cx)), count - 1) + offset;
  uint p1_idx = min(uint(ceil(cx)),  count - 1) + offset;

  line_point_t p0 = points[p0_idx];
  line_point_t p1 = points[p1_idx];

  float value = mix(p0.value, p1.value, fx);
  float alpha = value + v_coord.y - 1.0f;
  alpha = min(alpha / dFdy(v_coord.y), 1.0f);

  if (alpha <= 0.0f)
    discard;

  o_color = mix(
    unpackUnorm4x8(p0.color),
    unpackUnorm4x8(p1.color), fx);

  if (!srgbSwapchain)
    o_color.rgb = linearToSrgb(o_color.rgb);

  o_color *= alpha;
}


code:
#version 450

layout(location = 0) out vec2 o_coord;

layout(push_constant)
uniform push_data_t {
  uint offset;
  uint count;
  vec2 pos;
  vec2 size;
  vec2 scale;
};

void main() {
  vec2 coord = vec2(
    float(gl_VertexIndex  & 1),
    float(gl_VertexIndex >> 1));
  o_coord = coord;

  vec2 pixel_pos = pos + size * coord;
  vec2 scaled_pos = 2.0f * scale * pixel_pos - 1.0f;
  gl_Position = vec4(scaled_pos, 0.0f, 1.0f);
}


code:
#version 450

layout(constant_id = 1225) const bool srgbSwapchain = false;

layout(binding = 2) uniform sampler2D s_font;

layout(location = 0) in vec2 v_texcoord;
layout(location = 1) in vec4 v_color;

layout(location = 0) out vec4 o_color;

vec3 linearToSrgb(vec3 color) {
  bvec3 isLo = lessThanEqual(color, vec3(0.0031308f));

  vec3 loPart = color * 12.92f;
  vec3 hiPart = pow(color, vec3(5.0f / 12.0f)) * 1.055f - 0.055f;
  return mix(hiPart, loPart, isLo);
}

float sampleAlpha(float alpha_bias, float dist_range) {
  float value = textureLod(s_font, v_texcoord, 0).r + alpha_bias - 0.5f;
  float dist  = value * dot(vec2(dist_range, dist_range), 1.0f / fwidth(v_texcoord.xy));
  return clamp(dist + 0.5f, 0.0f, 1.0f);
}

void main() {
  float r_alpha_center = sampleAlpha(0.0f, 5.0f);
  float r_alpha_shadow = sampleAlpha(0.3f, 5.0f);

  vec3 r_center = v_color.rgb;
  vec3 r_shadow = vec3(0.0f, 0.0f, 0.0f);

  o_color.rgb = mix(r_shadow, r_center, r_alpha_center);
  o_color.a = r_alpha_shadow * v_color.a;
  o_color.rgb *= o_color.a;

  if (!srgbSwapchain)
    o_color.rgb = linearToSrgb(o_color.rgb);
}


code:
#version 450

struct font_info_t {
  float size;
  float advance;
  uvec2 padding;
};

struct glyph_info_t {
  uint packed_xy;
  uint packed_wh;
  uint packed_origin;
};

layout(binding = 0, std430)
readonly buffer font_buffer_t {
  font_info_t font_data;
  glyph_info_t glyph_data[];
};

layout(binding = 1) uniform usamplerBuffer text_buffer;

layout(push_constant)
uniform push_data_t {
  vec4 text_color;
  vec2 text_pos;
  uint text_offset;
  float text_size;
  vec2 hud_scale;
};

layout(location = 0) out vec2 o_texcoord;
layout(location = 1) out vec4 o_color;

const uvec2 coord_mask = uvec2(0x2a, 0x1c);

vec2 unpack_u16(uint v) {
  // Inputs may be signed
  int hi = int(v);
  int lo = int(v << 16);
  return vec2(float(lo >> 16), float(hi >> 16));
}

void main() {
  o_color = text_color;

  // Compute character index and vertex index for the current
  // character. We'll render two triangles per character.
  uint chr_idx = gl_VertexIndex / 6;
  uint vtx_idx = gl_VertexIndex - 6 * chr_idx;

  // Load glyph info based on vertex index
  uint glyph_idx = texelFetch(text_buffer, int(text_offset + chr_idx)).x;
  glyph_info_t glyph_info = glyph_data[glyph_idx];

  // Compute texture coordinate from glyph data
  vec2 coord = vec2((coord_mask >> vtx_idx) & 0x1);

  vec2 tex_xy = unpack_u16(glyph_info.packed_xy);
  vec2 tex_wh = unpack_u16(glyph_info.packed_wh);

  o_texcoord = tex_xy + coord * tex_wh;

  // Compute vertex position. We can easily do this here since our
  // font is a monospace font, otherwise we'd need to preprocess
  // the strings to render in a compute shader.
  float size_factor = text_size / font_data.size;

  vec2 local_pos = tex_wh * coord - unpack_u16(glyph_info.packed_origin)
    + vec2(font_data.advance * float(chr_idx), 0.0f);
  vec2 pixel_pos = text_pos + size_factor * local_pos;
  vec2 scaled_pos = 2.0f * hud_scale * pixel_pos - 1.0f;

  gl_Position = vec4(scaled_pos, 0.0f, 1.0f);
}


code:
dxvk_shaders = files([
  'shaders/dxvk_blit_frag_1d.frag',
  'shaders/dxvk_blit_frag_2d.frag',
  'shaders/dxvk_blit_frag_3d.frag',

  'shaders/dxvk_clear_buffer_u.comp',
  'shaders/dxvk_clear_buffer_f.comp',
  'shaders/dxvk_clear_image1d_u.comp',
  'shaders/dxvk_clear_image1d_f.comp',
  'shaders/dxvk_clear_image1darr_u.comp',
  'shaders/dxvk_clear_image1darr_f.comp',
  'shaders/dxvk_clear_image2d_u.comp',
  'shaders/dxvk_clear_image2d_f.comp',
  'shaders/dxvk_clear_image2darr_u.comp',
  'shaders/dxvk_clear_image2darr_f.comp',
  'shaders/dxvk_clear_image3d_u.comp',
  'shaders/dxvk_clear_image3d_f.comp',

  'shaders/dxvk_copy_buffer_image.comp',
  'shaders/dxvk_copy_color_1d.frag',
  'shaders/dxvk_copy_color_2d.frag',
  'shaders/dxvk_copy_color_ms.frag',
  'shaders/dxvk_copy_depth_1d.frag',
  'shaders/dxvk_copy_depth_2d.frag',
  'shaders/dxvk_copy_depth_ms.frag',
  'shaders/dxvk_copy_depth_stencil_1d.frag',
  'shaders/dxvk_copy_depth_stencil_2d.frag',
  'shaders/dxvk_copy_depth_stencil_ms.frag',

  'shaders/dxvk_fullscreen_geom.geom',
  'shaders/dxvk_fullscreen_vert.vert',
  'shaders/dxvk_fullscreen_layer_vert.vert',

  'shaders/dxvk_pack_d24s8.comp',
  'shaders/dxvk_pack_d32s8.comp',

  'shaders/dxvk_present_frag.frag',
  'shaders/dxvk_present_frag_blit.frag',
  'shaders/dxvk_present_frag_ms.frag',
  'shaders/dxvk_present_frag_ms_amd.frag',
  'shaders/dxvk_present_vert.vert',

  'shaders/dxvk_resolve_frag_d.frag',
  'shaders/dxvk_resolve_frag_ds.frag',
  'shaders/dxvk_resolve_frag_f.frag',
  'shaders/dxvk_resolve_frag_f_amd.frag',
  'shaders/dxvk_resolve_frag_i.frag',
  'shaders/dxvk_resolve_frag_u.frag',

  'shaders/dxvk_unpack_d24s8_as_d32s8.comp',
  'shaders/dxvk_unpack_d24s8.comp',
  'shaders/dxvk_unpack_d32s8.comp',

  'hud/shaders/hud_graph_frag.frag',
  'hud/shaders/hud_graph_vert.vert',

  'hud/shaders/hud_text_frag.frag',
  'hud/shaders/hud_text_vert.vert',
])

dxvk_src = files([
  'dxvk_adapter.cpp',
  'dxvk_barrier.cpp',
  'dxvk_buffer.cpp',
  'dxvk_cmdlist.cpp',
  'dxvk_compute.cpp',
  'dxvk_context.cpp',
  'dxvk_cs.cpp',
  'dxvk_data.cpp',
  'dxvk_descriptor.cpp',
  'dxvk_device.cpp',
  'dxvk_device_filter.cpp',
  'dxvk_extensions.cpp',
  'dxvk_fence.cpp',
  'dxvk_format.cpp',
  'dxvk_framebuffer.cpp',
  'dxvk_gpu_event.cpp',
  'dxvk_gpu_query.cpp',
  'dxvk_graphics.cpp',
  'dxvk_image.cpp',
  'dxvk_instance.cpp',
  'dxvk_lifetime.cpp',
  'dxvk_memory.cpp',
  'dxvk_meta_blit.cpp',
  'dxvk_meta_clear.cpp',
  'dxvk_meta_copy.cpp',
  'dxvk_meta_mipgen.cpp',
  'dxvk_meta_pack.cpp',
  'dxvk_meta_resolve.cpp',
  'dxvk_openvr.cpp',
  'dxvk_openxr.cpp',
  'dxvk_options.cpp',
  'dxvk_pipecache.cpp',
  'dxvk_pipelayout.cpp',
  'dxvk_pipemanager.cpp',
  'dxvk_queue.cpp',
  'dxvk_renderpass.cpp',
  'dxvk_resource.cpp',
  'dxvk_sampler.cpp',
  'dxvk_shader.cpp',
  'dxvk_shader_key.cpp',
  'dxvk_signal.cpp',
  'dxvk_spec_const.cpp',
  'dxvk_staging.cpp',
  'dxvk_state_cache.cpp',
  'dxvk_stats.cpp',
  'dxvk_swapchain_blitter.cpp',
  'dxvk_unbound.cpp',
  'dxvk_util.cpp',

  'platform/dxvk_win32_exts.cpp',

  'hud/dxvk_hud.cpp',
  'hud/dxvk_hud_font.cpp',
  'hud/dxvk_hud_item.cpp',
  'hud/dxvk_hud_renderer.cpp',
])

thread_dep = dependency('threads')

dxvk_lib = static_library('dxvk', dxvk_src, glsl_generator.process(dxvk_shaders), dxvk_version,
  link_with           : [ util_lib, spirv_lib ],
  dependencies        : [ thread_dep, vkcommon_dep ] + dxvk_extradep,
  include_directories : [ dxvk_include_path ],
)

dxvk_dep = declare_dependency(
  link_with           : [ dxvk_lib ],
  include_directories : [ dxvk_include_path ],
)


code:
#include "../dxvk_platform_exts.h"

namespace dxvk {

  DxvkPlatformExts DxvkPlatformExts::s_instance;

  std::string_view DxvkPlatformExts::getName() {
    return "Win32 WSI";
  }

  DxvkNameSet DxvkPlatformExts::getInstanceExtensions() {
    DxvkNameSet names;
    names.add(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);

    return names;
  }

  DxvkNameSet DxvkPlatformExts::getDeviceExtensions(
          uint32_t      adapterId) {
    return DxvkNameSet();
  }

  void DxvkPlatformExts::initInstanceExtensions() {

  }

  void DxvkPlatformExts::initDeviceExtensions(
    const DxvkInstance* instance) {

  }

}


code:
#version 450

layout(set = 0, binding = 0)
uniform sampler1DArray s_texture;

layout(location = 0) in  vec2 i_pos;
layout(location = 0) out vec4 o_color;

layout(push_constant)
uniform push_block {
  vec3 p_src_coord0;
  vec3 p_src_coord1;
  uint p_layer_count;
};

void main() {
  float coord = mix(p_src_coord0.x, p_src_coord1.x, i_pos.x);
  o_color = texture(s_texture, vec2(coord, gl_Layer));
}


code:
#version 450

layout(set = 0, binding = 0)
uniform sampler2DArray s_texture;

layout(location = 0) in  vec2 i_pos;
layout(location = 0) out vec4 o_color;

layout(push_constant)
uniform push_block {
  vec3 p_src_coord0;
  vec3 p_src_coord1;
  uint p_layer_count;
};

void main() {
  vec2 coord = mix(p_src_coord0.xy, p_src_coord1.xy, i_pos);
  o_color = texture(s_texture, vec3(coord, gl_Layer));
}


code:
#version 450

layout(set = 0, binding = 0)
uniform sampler3D s_texture;

layout(location = 0) in  vec2 i_pos;
layout(location = 0) out vec4 o_color;

layout(push_constant)
uniform push_block {
  vec3 p_src_coord0;
  vec3 p_src_coord1;
  uint p_layer_count;
};

void main() {
  vec3 coord = mix(p_src_coord0, p_src_coord1,
    vec3(i_pos, (float(gl_Layer) + 0.5f) / float(p_layer_count)));
  o_color = texture(s_texture, coord);
}


code:
#version 450

layout(
  local_size_x = 128,
  local_size_y = 1,
  local_size_z = 1) in;

layout(binding = 0)
writeonly uniform imageBuffer dst;

layout(push_constant)
uniform u_info_t {
  vec4 clear_value;
  ivec4 dst_offset;
  ivec4 dst_extent;
} u_info;

void main() {
  int thread_id = int(gl_GlobalInvocationID.x);

  if (thread_id < u_info.dst_extent.x) {
    imageStore(dst,
      u_info.dst_offset.x + thread_id,
      u_info.clear_value);
  }
}


code:
#version 450

layout(
  local_size_x = 128,
  local_size_y = 1,
  local_size_z = 1) in;

layout(binding = 0)
writeonly uniform uimageBuffer dst;

layout(push_constant)
uniform u_info_t {
  uvec4 clear_value;
  ivec4 dst_offset;
  ivec4 dst_extent;
} u_info;

void main() {
  int thread_id = int(gl_GlobalInvocationID.x);

  if (thread_id < u_info.dst_extent.x) {
    imageStore(dst,
      u_info.dst_offset.x + thread_id,
      u_info.clear_value);
  }
}


code:
#version 450

layout(
  local_size_x = 64,
  local_size_y = 1,
  local_size_z = 1) in;

layout(binding = 0)
writeonly uniform image1D dst;

layout(push_constant)
uniform u_info_t {
  vec4 clear_value;
  ivec4 dst_offset;
  ivec4 dst_extent;
} u_info;

void main() {
  ivec3 thread_id = ivec3(gl_GlobalInvocationID);

  if (thread_id.x < u_info.dst_extent.x) {
    imageStore(dst,
      u_info.dst_offset.x + thread_id.x,
      u_info.clear_value);
  }
}


code:
#version 450

layout(
  local_size_x = 64,
  local_size_y = 1,
  local_size_z = 1) in;

layout(binding = 0)
writeonly uniform uimage1D dst;

layout(push_constant)
uniform u_info_t {
  uvec4 clear_value;
  ivec4 dst_offset;
  ivec4 dst_extent;
} u_info;

void main() {
  ivec3 thread_id = ivec3(gl_GlobalInvocationID);

  if (thread_id.x < u_info.dst_extent.x) {
    imageStore(dst,
      u_info.dst_offset.x + thread_id.x,
      u_info.clear_value);
  }
}


code:
#version 450

layout(
  local_size_x = 64,
  local_size_y = 1,
  local_size_z = 1) in;

layout(binding = 0)
writeonly uniform image1DArray dst;

layout(push_constant)
uniform u_info_t {
  vec4 clear_value;
  ivec4 dst_offset;
  ivec4 dst_extent;
} u_info;

void main() {
  ivec3 thread_id = ivec3(gl_GlobalInvocationID);

  if (thread_id.x < u_info.dst_extent.x) {
    imageStore(dst,
      ivec2(u_info.dst_offset.x + thread_id.x, thread_id.y),
      u_info.clear_value);
  }
}


code:
#version 450

layout(
  local_size_x = 64,
  local_size_y = 1,
  local_size_z = 1) in;

layout(binding = 0)
writeonly uniform uimage1DArray dst;

layout(push_constant)
uniform u_info_t {
  uvec4 clear_value;
  ivec4 dst_offset;
  ivec4 dst_extent;
} u_info;

void main() {
  ivec3 thread_id = ivec3(gl_GlobalInvocationID);

  if (thread_id.x < u_info.dst_extent.x) {
    imageStore(dst,
      ivec2(u_info.dst_offset.x + thread_id.x, thread_id.y),
      u_info.clear_value);
  }
}


code:
#version 450

layout(
  local_size_x = 8,
  local_size_y = 8,
  local_size_z = 1) in;

layout(binding = 0)
writeonly uniform image2D dst;

layout(push_constant)
uniform u_info_t {
  vec4 clear_value;
  ivec4 dst_offset;
  ivec4 dst_extent;
} u_info;

void main() {
  ivec3 thread_id = ivec3(gl_GlobalInvocationID);

  if (all(lessThan(thread_id.xy, u_info.dst_extent.xy))) {
    imageStore(dst,
      u_info.dst_offset.xy + thread_id.xy,
      u_info.clear_value);
  }
}


code:
#version 450

layout(
  local_size_x = 8,
  local_size_y = 8,
  local_size_z = 1) in;

layout(binding = 0)
writeonly uniform uimage2D dst;

layout(push_constant)
uniform u_info_t {
  uvec4 clear_value;
  ivec4 dst_offset;
  ivec4 dst_extent;
} u_info;

void main() {
  ivec3 thread_id = ivec3(gl_GlobalInvocationID);

  if (all(lessThan(thread_id.xy, u_info.dst_extent.xy))) {
    imageStore(dst,
      u_info.dst_offset.xy + thread_id.xy,
      u_info.clear_value);
  }
}


code:
#version 450

layout(
  local_size_x = 8,
  local_size_y = 8,
  local_size_z = 1) in;

layout(binding = 0)
writeonly uniform image2DArray dst;

layout(push_constant)
uniform u_info_t {
  vec4 clear_value;
  ivec4 dst_offset;
  ivec4 dst_extent;
} u_info;

void main() {
  ivec3 thread_id = ivec3(gl_GlobalInvocationID);

  if (all(lessThan(thread_id.xy, u_info.dst_extent.xy))) {
    imageStore(dst,
      ivec3(u_info.dst_offset.xy + thread_id.xy, thread_id.z),
      u_info.clear_value);
  }
}


code:
#version 450

layout(
  local_size_x = 8,
  local_size_y = 8,
  local_size_z = 1) in;

layout(binding = 0)
writeonly uniform uimage2DArray dst;

layout(push_constant)
uniform u_info_t {
  uvec4 clear_value;
  ivec4 dst_offset;
  ivec4 dst_extent;
} u_info;

void main() {
  ivec3 thread_id = ivec3(gl_GlobalInvocationID);

  if (all(lessThan(thread_id.xy, u_info.dst_extent.xy))) {
    imageStore(dst,
      ivec3(u_info.dst_offset.xy + thread_id.xy, thread_id.z),
      u_info.clear_value);
  }
}


code:
#version 450

layout(
  local_size_x = 4,
  local_size_y = 4,
  local_size_z = 4) in;

layout(binding = 0)
writeonly uniform image3D dst;

layout(push_constant)
uniform u_info_t {
  vec4 clear_value;
  ivec4 dst_offset;
  ivec4 dst_extent;
} u_info;

void main() {
  ivec3 thread_id = ivec3(gl_GlobalInvocationID);

  if (all(lessThan(thread_id.xyz, u_info.dst_extent.xyz))) {
    imageStore(dst,
      u_info.dst_offset.xyz + thread_id.xyz,
      u_info.clear_value);
  }
}


code:
#version 450

layout(
  local_size_x = 4,
  local_size_y = 4,
  local_size_z = 4) in;

layout(binding = 0)
writeonly uniform uimage3D dst;

layout(push_constant)
uniform u_info_t {
  uvec4 clear_value;
  ivec4 dst_offset;
  ivec4 dst_extent;
} u_info;

void main() {
  ivec3 thread_id = ivec3(gl_GlobalInvocationID);

  if (all(lessThan(thread_id.xyz, u_info.dst_extent.xyz))) {
    imageStore(dst,
      u_info.dst_offset.xyz + thread_id.xyz,
      u_info.clear_value);
  }
}


code:
#version 450

layout(
  local_size_x = 8,
  local_size_y = 8,
  local_size_z = 1) in;

layout(binding = 0) writeonly uniform uimageBuffer u_dst;
layout(binding = 1) uniform usamplerBuffer u_src;

layout(push_constant)
uniform u_info_t {
  uvec3 dst_offset;
  uvec3 src_offset;
  uvec3 extent;
  uvec2 dst_size;
  uvec2 src_size;
} u_info;

void main() {
  if (all(lessThan(gl_GlobalInvocationID, u_info.extent))) {
    uvec3 dst_coord = u_info.dst_offset + gl_GlobalInvocationID;
    uvec3 src_coord = u_info.src_offset + gl_GlobalInvocationID;

    uint dst_index = dst_coord.x + u_info.dst_size.x * (dst_coord.y + u_info.dst_size.y * dst_coord.z);
    uint src_index = src_coord.x + u_info.src_size.x * (src_coord.y + u_info.src_size.y * src_coord.z);

    imageStore(u_dst, int(dst_index), texelFetch(u_src, int(src_index)));
  }
}


code:
#version 450

layout(set = 0, binding = 0)
uniform sampler1DArray s_image;

layout(location = 0) out vec4 o_color;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  o_color = texelFetch(s_image,
    ivec2(gl_FragCoord.x + u_info.offset.x, gl_Layer), 0);
}


code:
#version 450

layout(set = 0, binding = 0)
uniform sampler2DArray s_image;

layout(location = 0) out vec4 o_color;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  o_color = texelFetch(s_image,
    ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer), 0);
}


code:
#version 450

layout(set = 0, binding = 0)
uniform sampler2DMSArray s_image;

layout(location = 0) out vec4 o_color;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  o_color = texelFetch(s_image,
    ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer),
    gl_SampleID);
}


code:
#version 450

layout(set = 0, binding = 0)
uniform sampler1DArray s_image;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  gl_FragDepth = texelFetch(s_image,
    ivec2(gl_FragCoord.x + u_info.offset.x, gl_Layer), 0).r;
}


code:
#version 450

layout(set = 0, binding = 0)
uniform sampler2DArray s_image;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  gl_FragDepth = texelFetch(s_image,
    ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer), 0).r;
}


code:
#version 450

layout(set = 0, binding = 0)
uniform sampler2DMSArray s_image;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  gl_FragDepth = texelFetch(s_image,
    ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer),
    gl_SampleID).r;
}


code:
#version 450

#extension GL_ARB_shader_stencil_export : enable

layout(set = 0, binding = 0)
uniform sampler1DArray s_depth;

layout(set = 0, binding = 1)
uniform usampler1DArray s_stencil;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  ivec2 coord = ivec2(gl_FragCoord.x + u_info.offset.x, gl_Layer);
  gl_FragDepth         = texelFetch(s_depth,   coord, 0).r;
  gl_FragStencilRefARB = int(texelFetch(s_stencil, coord, 0).r);
}


code:
#version 450

#extension GL_ARB_shader_stencil_export : enable

layout(set = 0, binding = 0)
uniform sampler2DArray s_depth;

layout(set = 0, binding = 1)
uniform usampler2DArray s_stencil;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  ivec3 coord = ivec3(gl_FragCoord.xy + u_info.offset.xy, gl_Layer);
  gl_FragDepth         = texelFetch(s_depth,   coord, 0).r;
  gl_FragStencilRefARB = int(texelFetch(s_stencil, coord, 0).r);
}


code:
#version 450

#extension GL_ARB_shader_stencil_export : enable

layout(set = 0, binding = 0)
uniform sampler2DMSArray s_depth;

layout(set = 0, binding = 1)
uniform usampler2DMSArray s_stencil;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  ivec3 coord = ivec3(gl_FragCoord.xy + u_info.offset.xy, gl_Layer);
  gl_FragDepth         = texelFetch(s_depth,   coord, gl_SampleID).r;
  gl_FragStencilRefARB = int(texelFetch(s_stencil, coord, gl_SampleID).r);
}


code:
#version 450

layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;

layout(location = 0) in  int  i_instance[3];
layout(location = 1) in  vec2 i_texcoord[3];
layout(location = 0) out vec2 o_texcoord;

void main() {
  for (int i = 0; i < 3; i++) {
    o_texcoord  = i_texcoord[i];
    gl_Layer    = i_instance[i];
    gl_Position = gl_in[i].gl_Position;
    EmitVertex();
  }

  EndPrimitive();
}


code:
#version 450

#extension GL_ARB_shader_viewport_layer_array : enable

layout(location = 0) out vec2 o_texcoord;

void main() {
  vec2 coord = vec2(
    float(gl_VertexIndex & 2),
    float(gl_VertexIndex & 1) * 2.0f);

  o_texcoord  = coord;
  gl_Layer    = gl_InstanceIndex;
  gl_Position = vec4(-1.0f + 2.0f * coord, 0.0f, 1.0f);
}


code:
#version 450

layout(location = 0) out int  o_instance;
layout(location = 1) out vec2 o_texcoord;

void main() {
  vec2 coord = vec2(
    float(gl_VertexIndex & 2),
    float(gl_VertexIndex & 1) * 2.0f);

  o_instance  = gl_InstanceIndex;
  o_texcoord  = coord;
  gl_Position = vec4(-1.0f + 2.0f * coord, 0.0f, 1.0f);
}


code:
#version 450

layout(
  local_size_x = 8,
  local_size_y = 8,
  local_size_z = 1) in;

layout(binding = 0, std430)
writeonly buffer s_buffer_t {
  uint data[];
} s_buffer;

layout(binding = 1) uniform  sampler2DArray u_depth;
layout(binding = 2) uniform usampler2DArray u_stencil;

layout(push_constant)
uniform u_info_t {
  uvec2 src_offset;
  uvec2 src_extent;
  uvec2 dst_offset;
  uvec2 dst_extent;
} u_info;

void main() {
  if (all(lessThan(gl_GlobalInvocationID.xy, u_info.src_extent))) {
    uvec3 src_coord = uvec3(
      gl_GlobalInvocationID.xy + u_info.src_offset,
      gl_GlobalInvocationID.z);

    uvec3 dst_coord = uvec3(
      gl_GlobalInvocationID.xy + u_info.dst_offset,
      gl_GlobalInvocationID.z);

    uint dst_index = dst_coord.x + u_info.dst_extent.x * (dst_coord.y + u_info.dst_extent.y * dst_coord.z);

    float depth   = texelFetch(u_depth,   ivec3(src_coord), 0).r;
    uint  stencil = texelFetch(u_stencil, ivec3(src_coord), 0).r;

    s_buffer.data[dst_index] = uint(mix(0.0f, float((1 << 24) - 1), depth)) | (stencil << 24);
  }
}


code:
#version 450

layout(
  local_size_x = 8,
  local_size_y = 8,
  local_size_z = 1) in;

struct d32s8_t {
  float d32;
  uint  s8;
};

layout(binding = 0, std430)
writeonly buffer s_buffer_t {
  d32s8_t data[];
} s_buffer;

layout(binding = 1) uniform  sampler2DArray u_depth;
layout(binding = 2) uniform usampler2DArray u_stencil;

layout(push_constant)
uniform u_info_t {
  uvec2 src_offset;
  uvec2 src_extent;
  uvec2 dst_offset;
  uvec2 dst_extent;
} u_info;

void main() {
  if (all(lessThan(gl_GlobalInvocationID.xy, u_info.src_extent))) {
    uvec3 src_coord = uvec3(
      gl_GlobalInvocationID.xy + u_info.src_offset,
      gl_GlobalInvocationID.z);

    uvec3 dst_coord = uvec3(
      gl_GlobalInvocationID.xy + u_info.dst_offset,
      gl_GlobalInvocationID.z);

    uint dst_index = dst_coord.x + u_info.dst_extent.x * (dst_coord.y + u_info.dst_extent.y * dst_coord.z);

    s_buffer.data[dst_index].d32 = texelFetch(u_depth,   ivec3(src_coord), 0).r;
    s_buffer.data[dst_index].s8  = texelFetch(u_stencil, ivec3(src_coord), 0).r;
  }
}


code:
#version 450

layout(constant_id = 1) const bool s_gamma_bound = true;

layout(binding = 0) uniform sampler2D s_image;
layout(binding = 1) uniform sampler1D s_gamma;

layout(location = 0) out vec4 o_color;

layout(push_constant)
uniform present_info_t {
  ivec2 src_offset;
  ivec2 dst_offset;
};

void main() {
  ivec2 coord = ivec2(gl_FragCoord.xy) + src_offset - dst_offset;
  o_color = texelFetch(s_image, coord, 0);

  if (s_gamma_bound) {
    o_color = vec4(
      texture(s_gamma, o_color.r).r,
      texture(s_gamma, o_color.g).g,
      texture(s_gamma, o_color.b).b,
      o_color.a);
  }
}


code:
#version 450

layout(constant_id = 1) const bool s_gamma_bound = true;

layout(binding = 0) uniform sampler2D s_image;
layout(binding = 1) uniform sampler1D s_gamma;

layout(location = 0) in  vec2 i_coord;
layout(location = 0) out vec4 o_color;

layout(push_constant)
uniform present_info_t {
  ivec2 src_offset;
  uvec2 src_extent;
};

void main() {
  vec2 coord = vec2(src_offset) + vec2(src_extent) * i_coord;
  o_color = textureLod(s_image, coord, 0.0f);

  if (s_gamma_bound) {
    o_color = vec4(
      texture(s_gamma, o_color.r).r,
      texture(s_gamma, o_color.g).g,
      texture(s_gamma, o_color.b).b,
      o_color.a);
  }
}


code:
#version 450

layout(constant_id = 1) const bool s_gamma_bound = true;
layout(constant_id = 1225) const uint c_samples = 0;

layout(binding = 0) uniform sampler2DMS s_image;
layout(binding = 1) uniform sampler1D s_gamma;

layout(location = 0) out vec4 o_color;

layout(push_constant)
uniform present_info_t {
  ivec2 src_offset;
  ivec2 dst_offset;
};

void main() {
  ivec2 coord = ivec2(gl_FragCoord.xy) + src_offset - dst_offset;
  o_color = texelFetch(s_image, coord, 0);

  for (uint i = 1; i < c_samples; i++)
    o_color += texelFetch(s_image, coord, int(i));

  o_color /= float(c_samples);

  if (s_gamma_bound) {
    o_color = vec4(
      texture(s_gamma, o_color.r).r,
      texture(s_gamma, o_color.g).g,
      texture(s_gamma, o_color.b).b,
      o_color.a);
  }
}


code:
#version 450

#extension GL_AMD_shader_fragment_mask: enable

layout(constant_id = 1) const bool s_gamma_bound = true;
layout(constant_id = 1225) const uint c_samples = 0;

layout(binding = 0) uniform sampler2DMS s_image;
layout(binding = 1) uniform sampler1D s_gamma;

layout(location = 0) out vec4 o_color;

layout(push_constant)
uniform present_info_t {
  ivec2 src_offset;
  ivec2 dst_offset;
};

void main() {
  ivec2 coord = ivec2(gl_FragCoord.xy) + src_offset - dst_offset;

  // check dxvk_resolve_frag_f_amd.frag for documentation
  uint fragMask = fragmentMaskFetchAMD(s_image, coord);
  uint fragCount = 0u;

  for (int i = 0; i < 4 * c_samples; i += 4) {
    uint fragIndex = bitfieldExtract(fragMask, i, 4);
    fragCount += 1u << (fragIndex << 2);
  }

  o_color = vec4(0.0f);

  while (fragCount != 0) {
    int fragIndex = findLSB(fragCount) >> 2;
    int fragShift = fragIndex << 2;

    o_color += fragmentFetchAMD(s_image, coord, fragIndex)
      * float(bitfieldExtract(fragCount, fragShift, 4));

    fragCount = bitfieldInsert(fragCount, 0, fragShift, 4);
  }

  o_color /= float(c_samples);

  if (s_gamma_bound) {
    o_color = vec4(
      texture(s_gamma, o_color.r).r,
      texture(s_gamma, o_color.g).g,
      texture(s_gamma, o_color.b).b,
      o_color.a);
  }
}


code:
#version 450

layout(location = 0) out vec2 o_coord;

void main() {
  vec2 coord = vec2(
    float(gl_VertexIndex & 2),
    float(gl_VertexIndex & 1) * 2.0f);

  o_coord = coord;
  gl_Position = vec4(-1.0f + 2.0f * coord, 0.0f, 1.0f);
}


code:
#version 450

#define VK_RESOLVE_MODE_NONE_KHR            (0)
#define VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR (1 << 0)
#define VK_RESOLVE_MODE_AVERAGE_BIT_KHR     (1 << 1)
#define VK_RESOLVE_MODE_MIN_BIT_KHR         (1 << 2)
#define VK_RESOLVE_MODE_MAX_BIT_KHR         (1 << 3)

layout(constant_id = 0) const int c_samples = 1;
layout(constant_id = 1) const int c_mode_d  = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR;

layout(binding = 0) uniform sampler2DMSArray s_depth;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

float resolve_depth(ivec3 coord) {
  float depth = 0.0f;

  switch (c_mode_d) {
    case VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR:
      depth = texelFetch(s_depth, coord, 0).r;
      break;

    case VK_RESOLVE_MODE_AVERAGE_BIT_KHR:
      depth = texelFetch(s_depth, coord, 0).r;
      for (int i = 1; i < c_samples; i++)
        depth += texelFetch(s_depth, coord, i).r;
      depth /= float(c_samples);
      break;

    case VK_RESOLVE_MODE_MIN_BIT_KHR:
      depth = texelFetch(s_depth, coord, 0).r;
      for (int i = 1; i < c_samples; i++)
        depth = min(depth, texelFetch(s_depth, coord, i).r);
      break;

    case VK_RESOLVE_MODE_MAX_BIT_KHR:
      depth = texelFetch(s_depth, coord, 0).r;
      for (int i = 1; i < c_samples; i++)
        depth = max(depth, texelFetch(s_depth, coord, i).r);
      break;
  }

  return depth;
}

void main() {
  ivec3 coord = ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer);

  gl_FragDepth = resolve_depth(coord);
}


code:
#version 450

#extension GL_ARB_shader_stencil_export : enable

#define VK_RESOLVE_MODE_NONE_KHR            (0)
#define VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR (1 << 0)
#define VK_RESOLVE_MODE_AVERAGE_BIT_KHR     (1 << 1)
#define VK_RESOLVE_MODE_MIN_BIT_KHR         (1 << 2)
#define VK_RESOLVE_MODE_MAX_BIT_KHR         (1 << 3)

layout(constant_id = 0) const int c_samples = 1;
layout(constant_id = 1) const int c_mode_d  = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR;
layout(constant_id = 2) const int c_mode_s  = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR;

layout(binding = 0) uniform  sampler2DMSArray s_depth;
layout(binding = 1) uniform usampler2DMSArray s_stencil;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

float resolve_depth(ivec3 coord) {
  float depth = 0.0f;

  switch (c_mode_d) {
    case VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR:
      depth = texelFetch(s_depth, coord, 0).r;
      break;

    case VK_RESOLVE_MODE_AVERAGE_BIT_KHR:
      depth = texelFetch(s_depth, coord, 0).r;
      for (int i = 1; i < c_samples; i++)
        depth += texelFetch(s_depth, coord, i).r;
      depth /= float(c_samples);
      break;

    case VK_RESOLVE_MODE_MIN_BIT_KHR:
      depth = texelFetch(s_depth, coord, 0).r;
      for (int i = 1; i < c_samples; i++)
        depth = min(depth, texelFetch(s_depth, coord, i).r);
      break;

    case VK_RESOLVE_MODE_MAX_BIT_KHR:
      depth = texelFetch(s_depth, coord, 0).r;
      for (int i = 1; i < c_samples; i++)
        depth = max(depth, texelFetch(s_depth, coord, i).r);
      break;
  }

  return depth;
}

int resolve_stencil(ivec3 coord) {
  uint stencil = 0u;

  switch (c_mode_s) {
    case VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR:
      stencil = texelFetch(s_stencil, coord, 0).r;
      break;

    case VK_RESOLVE_MODE_MIN_BIT_KHR:
      stencil = texelFetch(s_stencil, coord, 0).r;
      for (int i = 1; i < c_samples; i++)
        stencil = min(stencil, texelFetch(s_stencil, coord, i).r);
      break;

    case VK_RESOLVE_MODE_MAX_BIT_KHR:
      stencil = texelFetch(s_stencil, coord, 0).r;
      for (int i = 1; i < c_samples; i++)
        stencil = max(stencil, texelFetch(s_stencil, coord, i).r);
      break;
  }

  return int(stencil);
}

void main() {
  ivec3 coord = ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer);

  gl_FragDepth         = resolve_depth(coord);
  gl_FragStencilRefARB = resolve_stencil(coord);
}


code:
#version 450

layout(constant_id = 0) const int c_samples = 1;

layout(binding = 0) uniform sampler2DMSArray s_image;

layout(location = 0) out vec4 o_color;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  ivec3 coord = ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer);
  vec4 color = vec4(0.0f);
  for (int i = 0; i < c_samples; i++)
    color += texelFetch(s_image, coord, i);
  o_color = color / float(c_samples);
}


code:
#version 450

#extension GL_AMD_shader_fragment_mask: enable

layout(constant_id = 0) const int c_samples = 1;

layout(set = 0, binding = 0)
uniform sampler2DMSArray s_image;

layout(location = 0) out vec4 o_color;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  ivec3 coord = ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer);

  // get a four-bit fragment index for each sample
  uint fragMask  = fragmentMaskFetchAMD(s_image, coord);

  // count number of occurences of each fragment
  // index in one four-bit counter for each sample
  uint fragCount = 0u;

  for (int i = 0; i < 4 * c_samples; i += 4) {
    uint fragIndex = bitfieldExtract(fragMask, i, 4);
    fragCount += 1u << (fragIndex << 2);
  }

  // perform necessary texture lookups to compute
  // final fragment color
  o_color = vec4(0.0f);

  while (fragCount != 0) {
    int fragIndex = findLSB(fragCount) >> 2;
    int fragShift = fragIndex << 2;

    o_color += fragmentFetchAMD(s_image, coord, fragIndex)
      * float(bitfieldExtract(fragCount, fragShift, 4));

    fragCount = bitfieldInsert(fragCount, 0, fragShift, 4);
  }

  o_color /= float(c_samples);
}


code:
#version 450

layout(binding = 0) uniform isampler2DMSArray s_image;

layout(location = 0) out ivec4 o_color;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  ivec3 coord = ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer);
  o_color = texelFetch(s_image, coord, 0);
}


code:
#version 450

layout(binding = 0) uniform usampler2DMSArray s_image;

layout(location = 0) out uvec4 o_color;

layout(push_constant)
uniform u_info_t {
  ivec2 offset;
} u_info;

void main() {
  ivec3 coord = ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer);
  o_color = texelFetch(s_image, coord, 0);
}


code:
#version 450

layout(
  local_size_x = 64,
  local_size_y = 1,
  local_size_z = 1) in;

layout(binding = 0, r32ui) writeonly uniform uimageBuffer u_depth;
layout(binding = 1, r8ui)  writeonly uniform uimageBuffer u_stencil;

layout(binding = 2)
readonly buffer s_buffer_t {
  uint data[];
} s_buffer;

layout(push_constant)
uniform u_info_t {
  uvec2 src_offset;
  uvec2 src_extent;
  uvec2 dst_offset;
  uvec2 dst_extent;
} u_info;

void main() {
  if (all(lessThan(gl_GlobalInvocationID.xy, u_info.dst_extent))) {
    uvec3 src_coord = uvec3(
      gl_GlobalInvocationID.xy + u_info.src_offset,
      gl_GlobalInvocationID.z);

    uvec3 dst_coord = uvec3(
      gl_GlobalInvocationID.xy + u_info.dst_offset,
      gl_GlobalInvocationID.z);

    uint src_index = src_coord.x + u_info.src_extent.x * (src_coord.y + u_info.src_extent.y * src_coord.z);
    uint dst_index = dst_coord.x + u_info.dst_extent.x * (dst_coord.y + u_info.dst_extent.y * dst_coord.z);

    uint src_data = s_buffer.data[src_index];
    imageStore(u_depth,   int(dst_index), uvec4(src_data & 0xFFFFFF));
    imageStore(u_stencil, int(dst_index), uvec4(src_data >> 24));
  }
}


code:
#version 450

layout(
  local_size_x = 64,
  local_size_y = 1,
  local_size_z = 1) in;

layout(binding = 0, r32f) writeonly uniform  imageBuffer u_depth;
layout(binding = 1, r8ui) writeonly uniform uimageBuffer u_stencil;

layout(binding = 2)
readonly buffer s_buffer_t {
  uint data[];
} s_buffer;

layout(push_constant)
uniform u_info_t {
  uvec2 src_offset;
  uvec2 src_extent;
  uvec2 dst_offset;
  uvec2 dst_extent;
} u_info;

void main() {
  if (all(lessThan(gl_GlobalInvocationID.xy, u_info.dst_extent))) {
    uvec3 src_coord = uvec3(
      gl_GlobalInvocationID.xy + u_info.src_offset,
      gl_GlobalInvocationID.z);

    uvec3 dst_coord = uvec3(
      gl_GlobalInvocationID.xy + u_info.dst_offset,
      gl_GlobalInvocationID.z);

    uint src_index = src_coord.x + u_info.src_extent.x * (src_coord.y + u_info.src_extent.y * src_coord.z);
    uint dst_index = dst_coord.x + u_info.dst_extent.x * (dst_coord.y + u_info.dst_extent.y * dst_coord.z);

    uint src_data = s_buffer.data[src_index];
    imageStore(u_depth,   int(dst_index),  vec4(float(src_data & 0xFFFFFF) / float(0xFFFFFF)));
    imageStore(u_stencil, int(dst_index), uvec4(src_data >> 24));
  }
}


code:
#version 450

layout(
  local_size_x = 64,
  local_size_y = 1,
  local_size_z = 1) in;

struct d32s8_t {
  float d32;
  uint  s8;
};

layout(binding = 0, r32f) writeonly uniform  imageBuffer u_depth;
layout(binding = 1, r8ui) writeonly uniform uimageBuffer u_stencil;

layout(binding = 2)
readonly buffer s_buffer_t {
  d32s8_t data[];
} s_buffer;

layout(push_constant)
uniform u_info_t {
  uvec2 src_offset;
  uvec2 src_extent;
  uvec2 dst_offset;
  uvec2 dst_extent;
} u_info;

void main() {
  if (all(lessThan(gl_GlobalInvocationID.xy, u_info.dst_extent))) {
    uvec3 src_coord = uvec3(
      gl_GlobalInvocationID.xy + u_info.src_offset,
      gl_GlobalInvocationID.z);

    uvec3 dst_coord = uvec3(
      gl_GlobalInvocationID.xy + u_info.dst_offset,
      gl_GlobalInvocationID.z);

    uint src_index = src_coord.x + u_info.src_extent.x * (src_coord.y + u_info.src_extent.y * src_coord.z);
    uint dst_index = dst_coord.x + u_info.dst_extent.x * (dst_coord.y + u_info.dst_extent.y * dst_coord.z);

    d32s8_t src_data = s_buffer.data[src_index];
    imageStore(u_depth,   int(dst_index),  vec4(src_data.d32));
    imageStore(u_stencil, int(dst_index), uvec4(src_data.s8));
  }
}


code:
subdir('util')
subdir('spirv')
subdir('vulkan')
subdir('dxvk')

if get_option('enable_dxgi')
  if not get_option('enable_d3d11')
    error('D3D11 is required for DXGI.')
  endif
  subdir('dxgi')
endif

if get_option('enable_d3d10') or get_option('enable_d3d11') or get_option('enable_tests')
  subdir('dxbc')
endif

if get_option('enable_d3d11')
  subdir('d3d11')
endif

if get_option('enable_d3d10')
  if not get_option('enable_d3d11')
    error('D3D11 is required for D3D10.')
  endif
  subdir('d3d10')
endif

if get_option('enable_d3d9')
  subdir('dxso')
  subdir('d3d9')
endif

# Nothing selected
if not get_option('enable_d3d9') and not get_option('enable_d3d10') and not get_option('enable_d3d11') and not get_option('enable_tests')
  warning('Nothing selected to be built. Are you missing a frontend or tests?')
endif


code:
spirv_src = files([
  'spirv_code_buffer.cpp',
  'spirv_compression.cpp',
  'spirv_module.cpp',
])

spirv_lib = static_library('spirv', spirv_src,
  include_directories : [ dxvk_include_path ],
)