[dxvk] Added HUD

Experimental version of a HUD which displays information
about the hardware, driver version, and frames per second.
This commit is contained in:
Philip Rebohle 2018-01-13 03:53:33 +01:00
parent 672675ba78
commit 96a97aa0c4
20 changed files with 2932 additions and 35 deletions

View File

@ -38,6 +38,7 @@ The behaviour of DXVK can be modified with environment variables.
- `DXVK_SHADER_DUMP_PATH=directory` Writes all DXBC and SPIR-V shaders to the given directory
- `DXVK_DEBUG_LAYERS=1` Enables Vulkan debug layers. Highly recommended for troubleshooting and debugging purposes.
- `DXVK_HUD=1` Enables the HUD
## Samples and executables
In addition to the DLLs, the following standalone programs are included in the project:

View File

@ -18,5 +18,11 @@ lib_d3d11 = dxvk_compiler.find_library('d3d11')
lib_dxgi = dxvk_compiler.find_library('dxgi')
lib_d3dcompiler_47 = dxvk_compiler.find_library('d3dcompiler_47')
glsl_compiler = find_program('glslangValidator')
glsl_generator = generator(glsl_compiler,
output : [ '@BASENAME@.h' ],
arguments : [ '-V', '--vn', '@BASENAME@', '@INPUT@', '-o', '@OUTPUT@' ])
subdir('src')
subdir('tests')

View File

@ -101,21 +101,17 @@ namespace dxvk {
loState.logicOp = VK_LOGIC_OP_NO_OP;
m_context->setLogicOpState(loState);
DxvkBlendMode blendMode;
blendMode.enableBlending = VK_FALSE;
blendMode.colorSrcFactor = VK_BLEND_FACTOR_ONE;
blendMode.colorDstFactor = VK_BLEND_FACTOR_ZERO;
blendMode.colorBlendOp = VK_BLEND_OP_ADD;
blendMode.alphaSrcFactor = VK_BLEND_FACTOR_ONE;
blendMode.alphaDstFactor = VK_BLEND_FACTOR_ZERO;
blendMode.alphaBlendOp = VK_BLEND_OP_ADD;
blendMode.writeMask = VK_COLOR_COMPONENT_R_BIT
| VK_COLOR_COMPONENT_G_BIT
| VK_COLOR_COMPONENT_B_BIT
| VK_COLOR_COMPONENT_A_BIT;
for (uint32_t i = 0; i < DxvkLimits::MaxNumRenderTargets; i++)
m_context->setBlendMode(i, blendMode);
m_blendMode.enableBlending = VK_FALSE;
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;
m_context->bindShader(
VK_SHADER_STAGE_VERTEX_BIT,
@ -124,6 +120,8 @@ namespace dxvk {
m_context->bindShader(
VK_SHADER_STAGE_FRAGMENT_BIT,
this->createFragmentShader());
m_hud = hud::Hud::createHud(m_device);
}
@ -150,16 +148,11 @@ namespace dxvk {
void DxgiPresenter::presentImage() {
auto newTime = std::chrono::high_resolution_clock::now();
auto us = std::chrono::duration_cast<std::chrono::microseconds>(newTime - m_oldTime).count();
m_frames += 1;
if (us >= 1'000'000) {
std::cout << "FPS: " << (static_cast<double>(m_frames * 1'000'000)
/ static_cast<double>(us)) << std::endl;
m_frames = 0;
m_oldTime = newTime;
if (m_hud != nullptr) {
m_hud->render({
m_options.preferredBufferSize.width,
m_options.preferredBufferSize.height,
});
}
const bool fitSize =
@ -207,9 +200,20 @@ namespace dxvk {
m_context->bindResourceSampler(BindingIds::Sampler,
fitSize ? m_samplerFitting : m_samplerScaling);
m_blendMode.enableBlending = VK_FALSE;
m_context->setBlendMode(0, m_blendMode);
m_context->bindResourceImage(BindingIds::Texture, m_backBufferView);
m_context->draw(4, 1, 0, 0);
if (m_hud != nullptr) {
m_blendMode.enableBlending = VK_TRUE;
m_context->setBlendMode(0, m_blendMode);
m_context->bindResourceImage(BindingIds::Texture, m_hud->texture());
m_context->draw(4, 1, 0, 0);
}
m_device->submitCommandList(
m_context->endRecording(),
sem.acquireSync, sem.presentSync);

View File

@ -1,15 +1,15 @@
#pragma once
#include <chrono>
#include "../dxvk/dxvk_device.h"
#include "../dxvk/dxvk_surface.h"
#include "../dxvk/dxvk_swapchain.h"
#include <dxvk_device.h>
#include <dxvk_surface.h>
#include <dxvk_swapchain.h>
#include "dxgi_include.h"
#include "../dxvk/hud/dxvk_hud.h"
#include "../spirv/spirv_module.h"
#include "dxgi_include.h"
namespace dxvk {
/**
@ -100,14 +100,14 @@ namespace dxvk {
Rc<DxvkImage> m_backBufferResolve;
Rc<DxvkImageView> m_backBufferView;
Rc<hud::Hud> m_hud;
DxvkBlendMode m_blendMode;
DxvkSwapchainProperties m_options;
Rc<DxvkShader> createVertexShader();
Rc<DxvkShader> createFragmentShader();
std::chrono::high_resolution_clock::time_point m_oldTime;
uint32_t m_frames = 0;
};
}

View File

@ -36,6 +36,7 @@ namespace dxvk::util {
}
bool operator == (VkExtent3D a, VkExtent3D b) {
return a.width == b.width
&& a.height == b.height
@ -48,3 +49,15 @@ bool operator != (VkExtent3D a, VkExtent3D b) {
|| a.height != b.height
|| a.depth != b.depth;
}
bool operator == (VkExtent2D a, VkExtent2D b) {
return a.width == b.width
&& a.height == b.height;
}
bool operator != (VkExtent2D a, VkExtent2D b) {
return a.width != b.width
|| a.height != b.height;
}

View File

@ -25,3 +25,6 @@ namespace dxvk::util {
bool operator == (VkExtent3D a, VkExtent3D b);
bool operator != (VkExtent3D a, VkExtent3D b);
bool operator == (VkExtent2D a, VkExtent2D b);
bool operator != (VkExtent2D a, VkExtent2D b);

248
src/dxvk/hud/dxvk_hud.cpp Normal file
View File

@ -0,0 +1,248 @@
#include "dxvk_hud.h"
#include <cstring>
namespace dxvk::hud {
Hud::Hud(const Rc<DxvkDevice>& device)
: m_device (device),
m_context (m_device->createContext()),
m_textRenderer (m_device, m_context),
m_uniformBuffer (createUniformBuffer()),
m_hudDeviceInfo (device) {
this->setupConstantState();
}
Hud::~Hud() {
}
void Hud::render(VkExtent2D size) {
bool recreateFbo = m_surfaceSize != size;
if (recreateFbo) {
m_surfaceSize = size;
this->setupFramebuffer(size);
}
m_hudFps.update();
this->synchronize();
this->updateUniformBuffer();
this->beginRenderPass(recreateFbo);
this->renderText();
this->endRenderPass();
}
Rc<Hud> Hud::createHud(const Rc<DxvkDevice>& device) {
const std::string hudConfig = env::getEnvVar(L"DXVK_HUD");
if (hudConfig.size() == 0)
return nullptr;
// TODO implement configuration options for the HUD
return new Hud(device);
}
Rc<DxvkBuffer> Hud::createUniformBuffer() {
DxvkBufferCreateInfo info;
info.size = sizeof(HudUniformData);
info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
info.stages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT
| VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
info.access = VK_ACCESS_UNIFORM_READ_BIT;
return m_device->createBuffer(info,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
}
void Hud::renderText() {
m_textRenderer.beginFrame(m_context);
HudPos position = { 8.0f, 24.0f };
position = m_hudDeviceInfo.renderText(
m_context, m_textRenderer, position);
position = m_hudFps.renderText(
m_context, m_textRenderer, position);
}
void Hud::synchronize() {
// Wait for previous frame to complete so that we can
// safely write to the uniform/vertex buffers. We could
// actually avoid this by double-buffering the data, but
// it is probably not worth the effort.
if (m_syncFence != nullptr) {
m_syncFence->wait(
std::numeric_limits<uint64_t>::max());
}
}
void Hud::updateUniformBuffer() {
HudUniformData uniformData;
uniformData.surfaceSize = m_surfaceSize;
std::memcpy(m_uniformBuffer->mapPtr(0),
&uniformData, sizeof(uniformData));
}
void Hud::beginRenderPass(bool initFbo) {
m_context->beginRecording(
m_device->createCommandList());
if (initFbo) {
m_context->initImage(m_renderTarget,
VkImageSubresourceRange {
VK_IMAGE_ASPECT_COLOR_BIT,
0, 1, 0, 1 });
}
VkClearAttachment clearInfo;
clearInfo.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
clearInfo.colorAttachment = 0;
for (uint32_t i = 0; i < 4; i++)
clearInfo.clearValue.color.float32[i] = 0.0f;
VkClearRect clearRect;
clearRect.rect.offset = { 0, 0 };
clearRect.rect.extent = m_surfaceSize;
clearRect.baseArrayLayer = 0;
clearRect.layerCount = 1;
m_context->bindFramebuffer(m_renderTargetFbo);
m_context->clearRenderTarget(clearInfo, clearRect);
VkViewport viewport;
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(m_surfaceSize.width);
viewport.height = static_cast<float>(m_surfaceSize.height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor;
scissor.offset = { 0, 0 };
scissor.extent = m_surfaceSize;
m_context->setViewports(1, &viewport, &scissor);
m_context->bindResourceBuffer(0, DxvkBufferSlice(m_uniformBuffer));
}
void Hud::endRenderPass() {
m_syncFence = m_device->submitCommandList(
m_context->endRecording(), nullptr, nullptr);
}
void Hud::setupFramebuffer(VkExtent2D size) {
DxvkImageCreateInfo imageInfo;
imageInfo.type = VK_IMAGE_TYPE_2D;
imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
imageInfo.flags = 0;
imageInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
imageInfo.extent = { size.width, size.height, 1 };
imageInfo.numLayers = 1;
imageInfo.mipLevels = 1;
imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
| VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
imageInfo.access = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
| VK_ACCESS_SHADER_READ_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
m_renderTarget = m_device->createImage(imageInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
DxvkImageViewCreateInfo viewInfo;
viewInfo.type = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = imageInfo.format;
viewInfo.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.minLevel = 0;
viewInfo.numLevels = 1;
viewInfo.minLayer = 0;
viewInfo.numLayers = 1;
m_renderTargetView = m_device->createImageView(m_renderTarget, viewInfo);
DxvkRenderTargets framebufferInfo;
framebufferInfo.setColorTarget(0, m_renderTargetView);
m_renderTargetFbo = m_device->createFramebuffer(framebufferInfo);
}
void Hud::setupConstantState() {
DxvkRasterizerState rsState;
rsState.enableDepthClamp = VK_FALSE;
rsState.enableDiscard = VK_FALSE;
rsState.polygonMode = VK_POLYGON_MODE_FILL;
rsState.cullMode = VK_CULL_MODE_BACK_BIT;
rsState.frontFace = VK_FRONT_FACE_CLOCKWISE;
rsState.depthBiasEnable = VK_FALSE;
rsState.depthBiasConstant = 0.0f;
rsState.depthBiasClamp = 0.0f;
rsState.depthBiasSlope = 0.0f;
m_context->setRasterizerState(rsState);
DxvkMultisampleState msState;
msState.sampleMask = 0xFFFFFFFF;
msState.enableAlphaToCoverage = VK_FALSE;
msState.enableAlphaToOne = VK_FALSE;
msState.enableSampleShading = VK_FALSE;
msState.minSampleShading = 1.0f;
m_context->setMultisampleState(msState);
VkStencilOpState stencilOp;
stencilOp.failOp = VK_STENCIL_OP_KEEP;
stencilOp.passOp = VK_STENCIL_OP_KEEP;
stencilOp.depthFailOp = VK_STENCIL_OP_KEEP;
stencilOp.compareOp = VK_COMPARE_OP_NEVER;
stencilOp.compareMask = 0xFFFFFFFF;
stencilOp.writeMask = 0xFFFFFFFF;
stencilOp.reference = 0;
DxvkDepthStencilState dsState;
dsState.enableDepthTest = VK_FALSE;
dsState.enableDepthWrite = VK_FALSE;
dsState.enableDepthBounds = VK_FALSE;
dsState.enableStencilTest = VK_FALSE;
dsState.depthCompareOp = VK_COMPARE_OP_NEVER;
dsState.stencilOpFront = stencilOp;
dsState.stencilOpBack = stencilOp;
m_context->setDepthStencilState(dsState);
DxvkLogicOpState loState;
loState.enableLogicOp = VK_FALSE;
loState.logicOp = VK_LOGIC_OP_NO_OP;
m_context->setLogicOpState(loState);
DxvkBlendMode blendMode;
blendMode.enableBlending = VK_TRUE;
blendMode.colorSrcFactor = VK_BLEND_FACTOR_ONE;
blendMode.colorDstFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
blendMode.colorBlendOp = VK_BLEND_OP_ADD;
blendMode.alphaSrcFactor = VK_BLEND_FACTOR_ONE;
blendMode.alphaDstFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
blendMode.alphaBlendOp = VK_BLEND_OP_ADD;
blendMode.writeMask = VK_COLOR_COMPONENT_R_BIT
| VK_COLOR_COMPONENT_G_BIT
| VK_COLOR_COMPONENT_B_BIT
| VK_COLOR_COMPONENT_A_BIT;
for (uint32_t i = 0; i < MaxNumRenderTargets; i++)
m_context->setBlendMode(i, blendMode);
}
}

94
src/dxvk/hud/dxvk_hud.h Normal file
View File

@ -0,0 +1,94 @@
#pragma once
#include "../dxvk_device.h"
#include "../util/util_env.h"
#include "dxvk_hud_devinfo.h"
#include "dxvk_hud_fps.h"
#include "dxvk_hud_text.h"
namespace dxvk::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:
explicit Hud(
const Rc<DxvkDevice>& device);
~Hud();
/**
* \brief Renders the HUD
*
* Recreates the render targets for the HUD
* in case the surface size has changed.
* \param [in] size Render target size
*/
void render(VkExtent2D size);
/**
* \brief Rendered image
*
* Returns the rendered image from
* the previous call to \ref render.
* \returns The image view
*/
Rc<DxvkImageView> texture() const {
return m_renderTargetView;
}
/**
* \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;
const Rc<DxvkContext> m_context;
HudTextRenderer m_textRenderer;
VkExtent2D m_surfaceSize = { 0, 0 };
Rc<DxvkFence> m_syncFence;
Rc<DxvkBuffer> m_uniformBuffer;
Rc<DxvkImage> m_renderTarget;
Rc<DxvkImageView> m_renderTargetView;
Rc<DxvkFramebuffer> m_renderTargetFbo;
HudDeviceInfo m_hudDeviceInfo;
HudFps m_hudFps;
void renderText();
Rc<DxvkBuffer> createUniformBuffer();
void synchronize();
void updateUniformBuffer();
void beginRenderPass(bool initFbo);
void endRenderPass();
void setupFramebuffer(VkExtent2D size);
void setupConstantState();
};
}

View File

@ -0,0 +1,46 @@
#include "dxvk_hud_devinfo.h"
namespace dxvk::hud {
HudDeviceInfo::HudDeviceInfo(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));
}
HudDeviceInfo::~HudDeviceInfo() {
}
HudPos HudDeviceInfo::renderText(
const Rc<DxvkContext>& context,
HudTextRenderer& renderer,
HudPos position) {
renderer.drawText(context, 16.0f,
{ position.x, position.y },
{ 0xFF, 0xFF, 0xFF, 0xFF },
m_deviceName);
renderer.drawText(context, 16.0f,
{ position.x, position.y + 24 },
{ 0xFF, 0xFF, 0xFF, 0xFF },
m_driverVer);
renderer.drawText(context, 16.0f,
{ position.x, position.y + 44 },
{ 0xFF, 0xFF, 0xFF, 0xFF },
m_vulkanVer);
return HudPos { position.x, position.y + 68 };
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "dxvk_hud_text.h"
namespace dxvk::hud {
/**
* \brief Device info display for the HUD
*
* Displays the name of the device, as well as
* the driver version and Vulkan API version.
*/
class HudDeviceInfo {
public:
HudDeviceInfo(const Rc<DxvkDevice>& device);
~HudDeviceInfo();
HudPos renderText(
const Rc<DxvkContext>& context,
HudTextRenderer& renderer,
HudPos position);
private:
std::string m_deviceName;
std::string m_driverVer;
std::string m_vulkanVer;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
#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;
}

View File

@ -0,0 +1,47 @@
#include "dxvk_hud_fps.h"
#include <iomanip>
namespace dxvk::hud {
HudFps::HudFps()
: m_fpsString("FPS: "),
m_prevUpdate(Clock::now()) {
}
HudFps::~HudFps() {
}
void HudFps::update() {
m_frameCount += 1;
const TimePoint now = Clock::now();
const TimeDiff elapsed = std::chrono::duration_cast<TimeDiff>(now - m_prevUpdate);
if (elapsed.count() >= UpdateInterval) {
const int64_t fps = (10'000'000ll * m_frameCount) / elapsed.count();
m_fpsString = str::format("FPS: ", fps / 10, ".", fps % 10);
m_prevUpdate = now;
m_frameCount = 0;
}
}
HudPos HudFps::renderText(
const Rc<DxvkContext>& context,
HudTextRenderer& renderer,
HudPos position) {
renderer.drawText(context, 16.0f,
{ position.x, position.y },
{ 0xFF, 0xFF, 0xFF, 0xFF },
m_fpsString);
return HudPos { position.x, position.y + 20 };
}
}

View File

@ -0,0 +1,42 @@
#pragma once
#include <chrono>
#include "dxvk_hud_text.h"
namespace dxvk::hud {
/**
* \brief FPS display for the HUD
*
* Displays the current frames per second.
* TODO implement frame time info/graph.
*/
class HudFps {
using Clock = std::chrono::high_resolution_clock;
using TimeDiff = std::chrono::microseconds;
using TimePoint = typename Clock::time_point;
constexpr static int64_t UpdateInterval = 500'000;
public:
HudFps();
~HudFps();
void update();
HudPos renderText(
const Rc<DxvkContext>& context,
HudTextRenderer& renderer,
HudPos position);
private:
std::string m_fpsString;
TimePoint m_prevUpdate;
int64_t m_frameCount = 0;
};
}

View File

@ -0,0 +1,278 @@
#include "dxvk_hud_text.h"
#include <hud_text_frag.h>
#include <hud_text_vert.h>
namespace dxvk::hud {
HudTextRenderer::HudTextRenderer(
const Rc<DxvkDevice>& device,
const Rc<DxvkContext>& context)
: m_vertShader (createVertexShader(device)),
m_fragShader (createFragmentShader(device)),
m_fontImage (createFontImage(device)),
m_fontView (createFontView(device)),
m_fontSampler (createFontSampler(device)),
m_vertexBuffer (createVertexBuffer(device)) {
this->initFontTexture(device, context);
this->initCharMap();
}
HudTextRenderer::~HudTextRenderer() {
}
void HudTextRenderer::beginFrame(const Rc<DxvkContext>& context) {
context->bindShader(VK_SHADER_STAGE_VERTEX_BIT, m_vertShader);
context->bindShader(VK_SHADER_STAGE_FRAGMENT_BIT, m_fragShader);
DxvkInputAssemblyState iaState;
iaState.primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
iaState.primitiveRestart = VK_FALSE;
context->setInputAssemblyState(iaState);
const std::array<DxvkVertexAttribute, 3> ilAttributes = {{
{ 0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(HudTextVertex, position) },
{ 1, 0, VK_FORMAT_R16G16_UINT, offsetof(HudTextVertex, texcoord) },
{ 2, 0, VK_FORMAT_R8G8B8A8_SRGB, offsetof(HudTextVertex, color) },
}};
const std::array<DxvkVertexBinding, 1> ilBindings = {{
{ 0, VK_VERTEX_INPUT_RATE_VERTEX },
}};
context->setInputLayout(
ilAttributes.size(),
ilAttributes.data(),
ilBindings.size(),
ilBindings.data());
context->bindVertexBuffer(0,
DxvkBufferSlice(m_vertexBuffer),
sizeof(HudTextVertex));
context->bindResourceSampler(1, m_fontSampler);
context->bindResourceImage (2, m_fontView);
m_vertexIndex = 0;
}
void HudTextRenderer::drawText(
const Rc<DxvkContext>& context,
float size,
HudPos pos,
HudColor color,
const std::string& text) {
const size_t vertexIndex = m_vertexIndex;
HudTextVertex* vertexData = reinterpret_cast<HudTextVertex*>(
m_vertexBuffer->mapPtr(vertexIndex * sizeof(HudTextVertex)));
const float sizeFactor = size / static_cast<float>(g_hudFont.size);
for (size_t i = 0; i < text.size(); i++) {
const HudGlyph& glyph = g_hudFont.glyphs[
m_charMap[static_cast<uint8_t>(text[i])]];
const HudPos size = {
sizeFactor * static_cast<float>(glyph.w),
sizeFactor * static_cast<float>(glyph.h) };
const HudPos origin = {
pos.x + sizeFactor * static_cast<float>(glyph.originX),
pos.y - sizeFactor * static_cast<float>(glyph.originY) };
const HudPos posTl = { origin.x, origin.y };
const HudPos posBr = { origin.x + size.x, origin.y + size.y };
const HudTexCoord texTl = {
static_cast<uint16_t>(glyph.x),
static_cast<uint16_t>(glyph.y), };
const HudTexCoord texBr = {
static_cast<uint16_t>(glyph.x + glyph.w),
static_cast<uint16_t>(glyph.y + glyph.h) };
vertexData[6 * i + 0].position = { posTl.x, posTl.y };
vertexData[6 * i + 0].texcoord = { texTl.u, texTl.v };
vertexData[6 * i + 0].color = color;
vertexData[6 * i + 1].position = { posBr.x, posTl.y };
vertexData[6 * i + 1].texcoord = { texBr.u, texTl.v };
vertexData[6 * i + 1].color = color;
vertexData[6 * i + 2].position = { posTl.x, posBr.y };
vertexData[6 * i + 2].texcoord = { texTl.u, texBr.v };
vertexData[6 * i + 2].color = color;
vertexData[6 * i + 3].position = { posBr.x, posBr.y };
vertexData[6 * i + 3].texcoord = { texBr.u, texBr.v };
vertexData[6 * i + 3].color = color;
vertexData[6 * i + 4].position = { posTl.x, posBr.y };
vertexData[6 * i + 4].texcoord = { texTl.u, texBr.v };
vertexData[6 * i + 4].color = color;
vertexData[6 * i + 5].position = { posBr.x, posTl.y };
vertexData[6 * i + 5].texcoord = { texBr.u, texTl.v };
vertexData[6 * i + 5].color = color;
pos.x += sizeFactor * static_cast<float>(g_hudFont.advance);
}
const uint32_t vertexCount = 6 * text.size();
context->draw(vertexCount, 1, vertexIndex, 0);
m_vertexIndex += vertexCount;
}
Rc<DxvkShader> HudTextRenderer::createVertexShader(const Rc<DxvkDevice>& device) {
const SpirvCodeBuffer codeBuffer(hud_text_vert);
// One shader resource: Global HUD uniform buffer
const std::array<DxvkResourceSlot, 1> resourceSlots = {{
{ 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_IMAGE_VIEW_TYPE_MAX_ENUM },
}};
// 3 input registers, 2 output registers, tightly packed
const DxvkInterfaceSlots interfaceSlots = { 0x7, 0x3 };
return new DxvkShader(
VK_SHADER_STAGE_VERTEX_BIT,
resourceSlots.size(),
resourceSlots.data(),
interfaceSlots,
codeBuffer);
}
Rc<DxvkShader> HudTextRenderer::createFragmentShader(const Rc<DxvkDevice>& device) {
const SpirvCodeBuffer codeBuffer(hud_text_frag);
// One shader resource: Global HUD uniform buffer
const std::array<DxvkResourceSlot, 2> resourceSlots = {{
{ 1, VK_DESCRIPTOR_TYPE_SAMPLER, VK_IMAGE_VIEW_TYPE_MAX_ENUM },
{ 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_IMAGE_VIEW_TYPE_2D },
}};
// 2 input registers, 1 output register
const DxvkInterfaceSlots interfaceSlots = { 0x3, 0x1 };
return new DxvkShader(
VK_SHADER_STAGE_FRAGMENT_BIT,
resourceSlots.size(),
resourceSlots.data(),
interfaceSlots,
codeBuffer);
}
Rc<DxvkImage> HudTextRenderer::createFontImage(const Rc<DxvkDevice>& device) {
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 device->createImage(info, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
}
Rc<DxvkImageView> HudTextRenderer::createFontView(const Rc<DxvkDevice>& device) {
DxvkImageViewCreateInfo info;
info.type = VK_IMAGE_VIEW_TYPE_2D;
info.format = m_fontImage->info().format;
info.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
info.minLevel = 0;
info.numLevels = 1;
info.minLayer = 0;
info.numLayers = 1;
return device->createImageView(m_fontImage, info);
}
Rc<DxvkSampler> HudTextRenderer::createFontSampler(const Rc<DxvkDevice>& device) {
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 = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
info.usePixelCoord = VK_TRUE;
return device->createSampler(info);
}
Rc<DxvkBuffer> HudTextRenderer::createVertexBuffer(const Rc<DxvkDevice>& device) {
DxvkBufferCreateInfo info;
info.size = MaxVertexCount * sizeof(HudTextVertex);
info.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
info.stages = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
info.access = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
return device->createBuffer(info,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
}
void HudTextRenderer::initFontTexture(
const Rc<DxvkDevice>& device,
const Rc<DxvkContext>& context) {
context->beginRecording(
device->createCommandList());
context->initImage(m_fontImage,
VkImageSubresourceRange {
VK_IMAGE_ASPECT_COLOR_BIT,
0, 1, 0, 1 });
context->updateImage(m_fontImage,
VkImageSubresourceLayers {
VK_IMAGE_ASPECT_COLOR_BIT,
0, 0, 1 },
VkOffset3D { 0, 0, 0 },
VkExtent3D { g_hudFont.width, g_hudFont.height, 1 },
g_hudFont.texture,
g_hudFont.width,
g_hudFont.width * g_hudFont.height);
device->submitCommandList(
context->endRecording(),
nullptr, nullptr);
}
void HudTextRenderer::initCharMap() {
std::fill(m_charMap.begin(), m_charMap.end(), 0);
for (uint32_t i = 0; i < g_hudFont.charCount; i++)
m_charMap.at(g_hudFont.glyphs[i].codePoint) = i;
}
}

View File

@ -0,0 +1,119 @@
#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 Texture coordinates
*
* Absolute texture coordinates that are used
* to pick letters in the font texture.
*/
struct HudTexCoord {
uint16_t u;
uint16_t v;
};
/**
* \brief Color
*
* SRGB color with alpha channel. The text
* will use this color for the most part.
*/
struct HudColor {
uint8_t x;
uint8_t y;
uint8_t z;
uint8_t w;
};
/**
* \brief Text vertex
*/
struct HudTextVertex {
HudPos position;
HudTexCoord texcoord;
HudColor color;
};
/**
* \brief Text renderer for the HUD
*
* Can be used by the presentation backend to
* display performance and driver information.
*/
class HudTextRenderer {
constexpr static VkDeviceSize MaxVertexCount = 1 << 16;
public:
HudTextRenderer(
const Rc<DxvkDevice>& device,
const Rc<DxvkContext>& context);
~HudTextRenderer();
void beginFrame(
const Rc<DxvkContext>& context);
void drawText(
const Rc<DxvkContext>& context,
float size,
HudPos pos,
HudColor color,
const std::string& text);
private:
std::array<uint8_t, 256> m_charMap;
Rc<DxvkShader> m_vertShader;
Rc<DxvkShader> m_fragShader;
Rc<DxvkImage> m_fontImage;
Rc<DxvkImageView> m_fontView;
Rc<DxvkSampler> m_fontSampler;
Rc<DxvkBuffer> m_vertexBuffer;
size_t m_vertexIndex = 0;
Rc<DxvkShader> createVertexShader(
const Rc<DxvkDevice>& device);
Rc<DxvkShader> createFragmentShader(
const Rc<DxvkDevice>& device);
Rc<DxvkImage> createFontImage(
const Rc<DxvkDevice>& device);
Rc<DxvkImageView> createFontView(
const Rc<DxvkDevice>& device);
Rc<DxvkSampler> createFontSampler(
const Rc<DxvkDevice>& device);
Rc<DxvkBuffer> createVertexBuffer(
const Rc<DxvkDevice>& device);
void initFontTexture(
const Rc<DxvkDevice>& device,
const Rc<DxvkContext>& context);
void initCharMap();
};
}

View File

@ -0,0 +1,26 @@
#version 450
layout(set = 0, binding = 1) uniform sampler s_font;
layout(set = 0, binding = 2) uniform texture2D t_font;
layout(location = 0) in vec2 v_texcoord;
layout(location = 1) in vec4 v_color;
layout(location = 0) out vec4 o_color;
float sampleAlpha(float alpha_bias, float dist_range) {
float value = texture(sampler2D(t_font, s_font), v_texcoord).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);
vec4 r_center = vec4(v_color.rgb, v_color.a * r_alpha_center);
vec4 r_shadow = vec4(0.0f, 0.0f, 0.0f, r_alpha_shadow);
o_color = mix(r_shadow, r_center, r_alpha_center);
o_color.rgb *= o_color.a;
}

View File

@ -0,0 +1,21 @@
#version 450
layout(set = 0, binding = 0, std140)
uniform u_hud {
uvec2 size;
} g_hud;
layout(location = 0) in vec2 v_position;
layout(location = 1) in uvec2 v_texcoord;
layout(location = 2) in vec4 v_color;
layout(location = 0) out vec2 o_texcoord;
layout(location = 1) out vec4 o_color;
void main() {
o_texcoord = vec2(v_texcoord);
o_color = v_color;
vec2 pos = 2.0f * (v_position / vec2(g_hud.size)) - 1.0f;
gl_Position = vec4(pos, 0.0f, 1.0f);
}

View File

@ -1,3 +1,8 @@
dxvk_hud_shaders = files([
'hud/shaders/hud_text_frag.frag',
'hud/shaders/hud_text_vert.vert',
])
dxvk_src = files([
'dxvk_adapter.cpp',
'dxvk_barrier.cpp',
@ -31,6 +36,12 @@ dxvk_src = files([
'dxvk_sync.cpp',
'dxvk_util.cpp',
'hud/dxvk_hud.cpp',
'hud/dxvk_hud_devinfo.cpp',
'hud/dxvk_hud_font.cpp',
'hud/dxvk_hud_fps.cpp',
'hud/dxvk_hud_text.cpp',
'vulkan/dxvk_vulkan_extensions.cpp',
'vulkan/dxvk_vulkan_loader.cpp',
'vulkan/dxvk_vulkan_names.cpp',
@ -38,7 +49,7 @@ dxvk_src = files([
thread_dep = dependency('threads')
dxvk_lib = static_library('dxvk', dxvk_src,
dxvk_lib = static_library('dxvk', dxvk_src, glsl_generator.process(dxvk_hud_shaders),
link_with : [ util_lib, spirv_lib ],
dependencies : [ thread_dep, lib_vulkan, lib_sdl2 ],
include_directories : [ dxvk_include_path ])

View File

@ -23,6 +23,11 @@ namespace dxvk {
SpirvCodeBuffer();
SpirvCodeBuffer(uint32_t size, const uint32_t* data);
SpirvCodeBuffer(std::istream&& stream);
template<size_t N>
SpirvCodeBuffer(const uint32_t (&data)[N])
: SpirvCodeBuffer(N, data) { }
~SpirvCodeBuffer();
/**