Replaced text overlay with proper (imgui based) UI overlay class
This commit is contained in:
parent
5107cf3ed0
commit
f6af0bde03
10 changed files with 743 additions and 807 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
file(GLOB BASE_SRC *.cpp)
|
file(GLOB BASE_SRC "*.cpp" "../external/imgui/imgui.cpp" "../external/imgui/imgui_draw.cpp")
|
||||||
file(GLOB BASE_HEADERS *.hpp)
|
file(GLOB BASE_HEADERS "*.hpp")
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
add_library(base STATIC ${BASE_SRC})
|
add_library(base STATIC ${BASE_SRC})
|
||||||
|
|
|
||||||
|
|
@ -1,746 +0,0 @@
|
||||||
/*
|
|
||||||
* Text overlay class for displaying debug information
|
|
||||||
*
|
|
||||||
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
|
|
||||||
*
|
|
||||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <vector>
|
|
||||||
#include <sstream>
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
#include <vulkan/vulkan.h>
|
|
||||||
#include "VulkanTools.h"
|
|
||||||
#include "VulkanDebug.h"
|
|
||||||
#include "VulkanBuffer.hpp"
|
|
||||||
#include "VulkanDevice.hpp"
|
|
||||||
|
|
||||||
#if defined(__ANDROID__)
|
|
||||||
#include "VulkanAndroid.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "../external/stb/stb_font_consolas_24_latin1.inl"
|
|
||||||
|
|
||||||
// Defines for the STB font used
|
|
||||||
// STB font files can be found at http://nothings.org/stb/font/
|
|
||||||
#define STB_FONT_NAME stb_font_consolas_24_latin1
|
|
||||||
#define STB_FONT_WIDTH STB_FONT_consolas_24_latin1_BITMAP_WIDTH
|
|
||||||
#define STB_FONT_HEIGHT STB_FONT_consolas_24_latin1_BITMAP_HEIGHT
|
|
||||||
#define STB_FIRST_CHAR STB_FONT_consolas_24_latin1_FIRST_CHAR
|
|
||||||
#define STB_NUM_CHARS STB_FONT_consolas_24_latin1_NUM_CHARS
|
|
||||||
|
|
||||||
// Max. number of chars the text overlay buffer can hold
|
|
||||||
#define MAX_CHAR_COUNT 1024
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Mostly self-contained text overlay class
|
|
||||||
* @note Will only work with compatible render passes
|
|
||||||
*/
|
|
||||||
class VulkanTextOverlay
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
vks::VulkanDevice *vulkanDevice;
|
|
||||||
|
|
||||||
VkQueue queue;
|
|
||||||
VkFormat colorFormat;
|
|
||||||
VkFormat depthFormat;
|
|
||||||
|
|
||||||
uint32_t *frameBufferWidth;
|
|
||||||
uint32_t *frameBufferHeight;
|
|
||||||
|
|
||||||
VkSampler sampler;
|
|
||||||
VkImage image;
|
|
||||||
VkImageView view;
|
|
||||||
vks::Buffer vertexBuffer;
|
|
||||||
VkDeviceMemory imageMemory;
|
|
||||||
VkDescriptorPool descriptorPool;
|
|
||||||
VkDescriptorSetLayout descriptorSetLayout;
|
|
||||||
VkDescriptorSet descriptorSet;
|
|
||||||
VkPipelineLayout pipelineLayout;
|
|
||||||
VkPipelineCache pipelineCache;
|
|
||||||
VkPipeline pipeline;
|
|
||||||
VkRenderPass renderPass;
|
|
||||||
VkCommandPool commandPool;
|
|
||||||
std::vector<VkFramebuffer*> frameBuffers;
|
|
||||||
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
|
|
||||||
VkFence fence;
|
|
||||||
|
|
||||||
// Used during text updates
|
|
||||||
glm::vec4 *mappedLocal = nullptr;
|
|
||||||
|
|
||||||
stb_fontchar stbFontData[STB_NUM_CHARS];
|
|
||||||
uint32_t numLetters;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
enum TextAlign { alignLeft, alignCenter, alignRight };
|
|
||||||
|
|
||||||
bool visible = true;
|
|
||||||
bool invalidated = false;
|
|
||||||
|
|
||||||
float scale = 1.0f;
|
|
||||||
|
|
||||||
std::vector<VkCommandBuffer> cmdBuffers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default constructor
|
|
||||||
*
|
|
||||||
* @param vulkanDevice Pointer to a valid VulkanDevice
|
|
||||||
*/
|
|
||||||
VulkanTextOverlay(
|
|
||||||
vks::VulkanDevice *vulkanDevice,
|
|
||||||
VkQueue queue,
|
|
||||||
std::vector<VkFramebuffer> &framebuffers,
|
|
||||||
VkFormat colorformat,
|
|
||||||
VkFormat depthformat,
|
|
||||||
uint32_t *framebufferwidth,
|
|
||||||
uint32_t *framebufferheight,
|
|
||||||
std::vector<VkPipelineShaderStageCreateInfo> shaderstages)
|
|
||||||
{
|
|
||||||
this->vulkanDevice = vulkanDevice;
|
|
||||||
this->queue = queue;
|
|
||||||
this->colorFormat = colorformat;
|
|
||||||
this->depthFormat = depthformat;
|
|
||||||
|
|
||||||
this->frameBuffers.resize(framebuffers.size());
|
|
||||||
for (uint32_t i = 0; i < framebuffers.size(); i++)
|
|
||||||
{
|
|
||||||
this->frameBuffers[i] = &framebuffers[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
this->shaderStages = shaderstages;
|
|
||||||
|
|
||||||
this->frameBufferWidth = framebufferwidth;
|
|
||||||
this->frameBufferHeight = framebufferheight;
|
|
||||||
|
|
||||||
#if defined(__ANDROID__)
|
|
||||||
// Scale text on Android devices with high DPI
|
|
||||||
if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XXHIGH) {
|
|
||||||
LOGD("XXHIGH");
|
|
||||||
scale = 2.0f;
|
|
||||||
}
|
|
||||||
else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XHIGH) {
|
|
||||||
LOGD("XHIGH");
|
|
||||||
scale = 1.5f;
|
|
||||||
}
|
|
||||||
else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_HIGH) {
|
|
||||||
LOGD("HIGH");
|
|
||||||
scale = 1.25f;
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
cmdBuffers.resize(framebuffers.size());
|
|
||||||
prepareResources();
|
|
||||||
prepareRenderPass();
|
|
||||||
preparePipeline();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default destructor, frees up all Vulkan resources acquired by the text overlay
|
|
||||||
*/
|
|
||||||
~VulkanTextOverlay()
|
|
||||||
{
|
|
||||||
// Free up all Vulkan resources requested by the text overlay
|
|
||||||
vertexBuffer.destroy();
|
|
||||||
vkDestroySampler(vulkanDevice->logicalDevice, sampler, nullptr);
|
|
||||||
vkDestroyImage(vulkanDevice->logicalDevice, image, nullptr);
|
|
||||||
vkDestroyImageView(vulkanDevice->logicalDevice, view, nullptr);
|
|
||||||
vkFreeMemory(vulkanDevice->logicalDevice, imageMemory, nullptr);
|
|
||||||
vkDestroyDescriptorSetLayout(vulkanDevice->logicalDevice, descriptorSetLayout, nullptr);
|
|
||||||
vkDestroyDescriptorPool(vulkanDevice->logicalDevice, descriptorPool, nullptr);
|
|
||||||
vkDestroyPipelineLayout(vulkanDevice->logicalDevice, pipelineLayout, nullptr);
|
|
||||||
vkDestroyPipelineCache(vulkanDevice->logicalDevice, pipelineCache, nullptr);
|
|
||||||
vkDestroyPipeline(vulkanDevice->logicalDevice, pipeline, nullptr);
|
|
||||||
vkDestroyRenderPass(vulkanDevice->logicalDevice, renderPass, nullptr);
|
|
||||||
vkFreeCommandBuffers(vulkanDevice->logicalDevice, commandPool, static_cast<uint32_t>(cmdBuffers.size()), cmdBuffers.data());
|
|
||||||
vkDestroyCommandPool(vulkanDevice->logicalDevice, commandPool, nullptr);
|
|
||||||
vkDestroyFence(vulkanDevice->logicalDevice, fence, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare all vulkan resources required to render the font
|
|
||||||
* The text overlay uses separate resources for descriptors (pool, sets, layouts), pipelines and command buffers
|
|
||||||
*/
|
|
||||||
void prepareResources()
|
|
||||||
{
|
|
||||||
static unsigned char font24pixels[STB_FONT_HEIGHT][STB_FONT_WIDTH];
|
|
||||||
STB_FONT_NAME(stbFontData, font24pixels, STB_FONT_HEIGHT);
|
|
||||||
|
|
||||||
// Command buffer
|
|
||||||
|
|
||||||
// Pool
|
|
||||||
VkCommandPoolCreateInfo cmdPoolInfo = {};
|
|
||||||
cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
||||||
cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics;
|
|
||||||
cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
||||||
VK_CHECK_RESULT(vkCreateCommandPool(vulkanDevice->logicalDevice, &cmdPoolInfo, nullptr, &commandPool));
|
|
||||||
|
|
||||||
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
|
|
||||||
vks::initializers::commandBufferAllocateInfo(
|
|
||||||
commandPool,
|
|
||||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
|
||||||
(uint32_t)cmdBuffers.size());
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(vulkanDevice->logicalDevice, &cmdBufAllocateInfo, cmdBuffers.data()));
|
|
||||||
|
|
||||||
// Vertex buffer
|
|
||||||
VK_CHECK_RESULT(vulkanDevice->createBuffer(
|
|
||||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
|
||||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
|
||||||
&vertexBuffer,
|
|
||||||
MAX_CHAR_COUNT * sizeof(glm::vec4)));
|
|
||||||
|
|
||||||
// Map persistent
|
|
||||||
vertexBuffer.map();
|
|
||||||
|
|
||||||
// Font texture
|
|
||||||
VkImageCreateInfo imageInfo = vks::initializers::imageCreateInfo();
|
|
||||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
|
||||||
imageInfo.format = VK_FORMAT_R8_UNORM;
|
|
||||||
imageInfo.extent.width = STB_FONT_WIDTH;
|
|
||||||
imageInfo.extent.height = STB_FONT_HEIGHT;
|
|
||||||
imageInfo.extent.depth = 1;
|
|
||||||
imageInfo.mipLevels = 1;
|
|
||||||
imageInfo.arrayLayers = 1;
|
|
||||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
||||||
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
||||||
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
||||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
|
|
||||||
VK_CHECK_RESULT(vkCreateImage(vulkanDevice->logicalDevice, &imageInfo, nullptr, &image));
|
|
||||||
|
|
||||||
VkMemoryRequirements memReqs;
|
|
||||||
VkMemoryAllocateInfo allocInfo = vks::initializers::memoryAllocateInfo();
|
|
||||||
vkGetImageMemoryRequirements(vulkanDevice->logicalDevice, image, &memReqs);
|
|
||||||
allocInfo.allocationSize = memReqs.size;
|
|
||||||
allocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
||||||
VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &allocInfo, nullptr, &imageMemory));
|
|
||||||
VK_CHECK_RESULT(vkBindImageMemory(vulkanDevice->logicalDevice, image, imageMemory, 0));
|
|
||||||
|
|
||||||
// Staging
|
|
||||||
vks::Buffer stagingBuffer;
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vulkanDevice->createBuffer(
|
|
||||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
|
||||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
|
||||||
&stagingBuffer,
|
|
||||||
allocInfo.allocationSize));
|
|
||||||
|
|
||||||
stagingBuffer.map();
|
|
||||||
memcpy(stagingBuffer.mapped, &font24pixels[0][0], STB_FONT_WIDTH * STB_FONT_HEIGHT); // Only one channel, so data size = W * H (*R8)
|
|
||||||
stagingBuffer.unmap();
|
|
||||||
|
|
||||||
// Copy to image
|
|
||||||
VkCommandBuffer copyCmd;
|
|
||||||
cmdBufAllocateInfo.commandBufferCount = 1;
|
|
||||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(vulkanDevice->logicalDevice, &cmdBufAllocateInfo, ©Cmd));
|
|
||||||
|
|
||||||
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
|
|
||||||
VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
|
|
||||||
|
|
||||||
// Prepare for transfer
|
|
||||||
vks::tools::setImageLayout(
|
|
||||||
copyCmd,
|
|
||||||
image,
|
|
||||||
VK_IMAGE_ASPECT_COLOR_BIT,
|
|
||||||
VK_IMAGE_LAYOUT_PREINITIALIZED,
|
|
||||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
|
||||||
|
|
||||||
VkBufferImageCopy bufferCopyRegion = {};
|
|
||||||
bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
||||||
bufferCopyRegion.imageSubresource.mipLevel = 0;
|
|
||||||
bufferCopyRegion.imageSubresource.layerCount = 1;
|
|
||||||
bufferCopyRegion.imageExtent.width = STB_FONT_WIDTH;
|
|
||||||
bufferCopyRegion.imageExtent.height = STB_FONT_HEIGHT;
|
|
||||||
bufferCopyRegion.imageExtent.depth = 1;
|
|
||||||
|
|
||||||
vkCmdCopyBufferToImage(
|
|
||||||
copyCmd,
|
|
||||||
stagingBuffer.buffer,
|
|
||||||
image,
|
|
||||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
||||||
1,
|
|
||||||
&bufferCopyRegion
|
|
||||||
);
|
|
||||||
|
|
||||||
// Prepare for shader read
|
|
||||||
vks::tools::setImageLayout(
|
|
||||||
copyCmd,
|
|
||||||
image,
|
|
||||||
VK_IMAGE_ASPECT_COLOR_BIT,
|
|
||||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
||||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
|
|
||||||
|
|
||||||
VkSubmitInfo submitInfo = vks::initializers::submitInfo();
|
|
||||||
submitInfo.commandBufferCount = 1;
|
|
||||||
submitInfo.pCommandBuffers = ©Cmd;
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
|
|
||||||
VK_CHECK_RESULT(vkQueueWaitIdle(queue));
|
|
||||||
|
|
||||||
stagingBuffer.destroy();
|
|
||||||
|
|
||||||
vkFreeCommandBuffers(vulkanDevice->logicalDevice, commandPool, 1, ©Cmd);
|
|
||||||
|
|
||||||
VkImageViewCreateInfo imageViewInfo = vks::initializers::imageViewCreateInfo();
|
|
||||||
imageViewInfo.image = image;
|
|
||||||
imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
||||||
imageViewInfo.format = imageInfo.format;
|
|
||||||
imageViewInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
|
|
||||||
imageViewInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
|
|
||||||
VK_CHECK_RESULT(vkCreateImageView(vulkanDevice->logicalDevice, &imageViewInfo, nullptr, &view));
|
|
||||||
|
|
||||||
// Sampler
|
|
||||||
VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
|
|
||||||
samplerInfo.magFilter = VK_FILTER_LINEAR;
|
|
||||||
samplerInfo.minFilter = VK_FILTER_LINEAR;
|
|
||||||
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
|
||||||
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
||||||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
||||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
||||||
samplerInfo.mipLodBias = 0.0f;
|
|
||||||
samplerInfo.compareOp = VK_COMPARE_OP_NEVER;
|
|
||||||
samplerInfo.minLod = 0.0f;
|
|
||||||
samplerInfo.maxLod = 1.0f;
|
|
||||||
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
|
||||||
samplerInfo.maxAnisotropy = 1.0f;
|
|
||||||
VK_CHECK_RESULT(vkCreateSampler(vulkanDevice->logicalDevice, &samplerInfo, nullptr, &sampler));
|
|
||||||
|
|
||||||
// Descriptor
|
|
||||||
// Font uses a separate descriptor pool
|
|
||||||
std::array<VkDescriptorPoolSize, 1> poolSizes;
|
|
||||||
poolSizes[0] = vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1);
|
|
||||||
|
|
||||||
VkDescriptorPoolCreateInfo descriptorPoolInfo =
|
|
||||||
vks::initializers::descriptorPoolCreateInfo(
|
|
||||||
static_cast<uint32_t>(poolSizes.size()),
|
|
||||||
poolSizes.data(),
|
|
||||||
1);
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkCreateDescriptorPool(vulkanDevice->logicalDevice, &descriptorPoolInfo, nullptr, &descriptorPool));
|
|
||||||
|
|
||||||
// Descriptor set layout
|
|
||||||
std::array<VkDescriptorSetLayoutBinding, 1> setLayoutBindings;
|
|
||||||
setLayoutBindings[0] = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
|
|
||||||
|
|
||||||
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo =
|
|
||||||
vks::initializers::descriptorSetLayoutCreateInfo(
|
|
||||||
setLayoutBindings.data(),
|
|
||||||
static_cast<uint32_t>(setLayoutBindings.size()));
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(vulkanDevice->logicalDevice, &descriptorSetLayoutInfo, nullptr, &descriptorSetLayout));
|
|
||||||
|
|
||||||
// Pipeline layout
|
|
||||||
VkPipelineLayoutCreateInfo pipelineLayoutInfo =
|
|
||||||
vks::initializers::pipelineLayoutCreateInfo(
|
|
||||||
&descriptorSetLayout,
|
|
||||||
1);
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkCreatePipelineLayout(vulkanDevice->logicalDevice, &pipelineLayoutInfo, nullptr, &pipelineLayout));
|
|
||||||
|
|
||||||
// Descriptor set
|
|
||||||
VkDescriptorSetAllocateInfo descriptorSetAllocInfo =
|
|
||||||
vks::initializers::descriptorSetAllocateInfo(
|
|
||||||
descriptorPool,
|
|
||||||
&descriptorSetLayout,
|
|
||||||
1);
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkAllocateDescriptorSets(vulkanDevice->logicalDevice, &descriptorSetAllocInfo, &descriptorSet));
|
|
||||||
|
|
||||||
VkDescriptorImageInfo texDescriptor =
|
|
||||||
vks::initializers::descriptorImageInfo(
|
|
||||||
sampler,
|
|
||||||
view,
|
|
||||||
VK_IMAGE_LAYOUT_GENERAL);
|
|
||||||
|
|
||||||
std::array<VkWriteDescriptorSet, 1> writeDescriptorSets;
|
|
||||||
writeDescriptorSets[0] = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &texDescriptor);
|
|
||||||
vkUpdateDescriptorSets(vulkanDevice->logicalDevice, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
|
|
||||||
|
|
||||||
// Pipeline cache
|
|
||||||
VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
|
|
||||||
pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
|
|
||||||
VK_CHECK_RESULT(vkCreatePipelineCache(vulkanDevice->logicalDevice, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
|
|
||||||
|
|
||||||
// Command buffer execution fence
|
|
||||||
VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo();
|
|
||||||
VK_CHECK_RESULT(vkCreateFence(vulkanDevice->logicalDevice, &fenceCreateInfo, nullptr, &fence));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare a separate pipeline for the font rendering decoupled from the main application
|
|
||||||
*/
|
|
||||||
void preparePipeline()
|
|
||||||
{
|
|
||||||
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
|
|
||||||
vks::initializers::pipelineInputAssemblyStateCreateInfo(
|
|
||||||
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
|
|
||||||
0,
|
|
||||||
VK_FALSE);
|
|
||||||
|
|
||||||
VkPipelineRasterizationStateCreateInfo rasterizationState =
|
|
||||||
vks::initializers::pipelineRasterizationStateCreateInfo(
|
|
||||||
VK_POLYGON_MODE_FILL,
|
|
||||||
VK_CULL_MODE_BACK_BIT,
|
|
||||||
VK_FRONT_FACE_CLOCKWISE,
|
|
||||||
0);
|
|
||||||
|
|
||||||
// Enable blending
|
|
||||||
VkPipelineColorBlendAttachmentState blendAttachmentState =
|
|
||||||
vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_TRUE);
|
|
||||||
|
|
||||||
blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
|
||||||
blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
|
||||||
blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
|
|
||||||
blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
|
||||||
blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
|
||||||
blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
|
|
||||||
blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
|
||||||
|
|
||||||
VkPipelineColorBlendStateCreateInfo colorBlendState =
|
|
||||||
vks::initializers::pipelineColorBlendStateCreateInfo(
|
|
||||||
1,
|
|
||||||
&blendAttachmentState);
|
|
||||||
|
|
||||||
VkPipelineDepthStencilStateCreateInfo depthStencilState =
|
|
||||||
vks::initializers::pipelineDepthStencilStateCreateInfo(
|
|
||||||
VK_FALSE,
|
|
||||||
VK_FALSE,
|
|
||||||
VK_COMPARE_OP_LESS_OR_EQUAL);
|
|
||||||
|
|
||||||
VkPipelineViewportStateCreateInfo viewportState =
|
|
||||||
vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
|
|
||||||
|
|
||||||
VkPipelineMultisampleStateCreateInfo multisampleState =
|
|
||||||
vks::initializers::pipelineMultisampleStateCreateInfo(
|
|
||||||
VK_SAMPLE_COUNT_1_BIT,
|
|
||||||
0);
|
|
||||||
|
|
||||||
std::vector<VkDynamicState> dynamicStateEnables = {
|
|
||||||
VK_DYNAMIC_STATE_VIEWPORT,
|
|
||||||
VK_DYNAMIC_STATE_SCISSOR
|
|
||||||
};
|
|
||||||
|
|
||||||
VkPipelineDynamicStateCreateInfo dynamicState =
|
|
||||||
vks::initializers::pipelineDynamicStateCreateInfo(
|
|
||||||
dynamicStateEnables.data(),
|
|
||||||
static_cast<uint32_t>(dynamicStateEnables.size()),
|
|
||||||
0);
|
|
||||||
|
|
||||||
std::array<VkVertexInputBindingDescription, 2> vertexBindings = {};
|
|
||||||
vertexBindings[0] = vks::initializers::vertexInputBindingDescription(0, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX);
|
|
||||||
vertexBindings[1] = vks::initializers::vertexInputBindingDescription(1, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX);
|
|
||||||
|
|
||||||
std::array<VkVertexInputAttributeDescription, 2> vertexAttribs = {};
|
|
||||||
// Position
|
|
||||||
vertexAttribs[0] = vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, 0);
|
|
||||||
// UV
|
|
||||||
vertexAttribs[1] = vks::initializers::vertexInputAttributeDescription(1, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(glm::vec2));
|
|
||||||
|
|
||||||
VkPipelineVertexInputStateCreateInfo inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
|
|
||||||
inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexBindings.size());
|
|
||||||
inputState.pVertexBindingDescriptions = vertexBindings.data();
|
|
||||||
inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexAttribs.size());
|
|
||||||
inputState.pVertexAttributeDescriptions = vertexAttribs.data();
|
|
||||||
|
|
||||||
VkGraphicsPipelineCreateInfo pipelineCreateInfo =
|
|
||||||
vks::initializers::pipelineCreateInfo(
|
|
||||||
pipelineLayout,
|
|
||||||
renderPass,
|
|
||||||
0);
|
|
||||||
|
|
||||||
pipelineCreateInfo.pVertexInputState = &inputState;
|
|
||||||
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
|
|
||||||
pipelineCreateInfo.pRasterizationState = &rasterizationState;
|
|
||||||
pipelineCreateInfo.pColorBlendState = &colorBlendState;
|
|
||||||
pipelineCreateInfo.pMultisampleState = &multisampleState;
|
|
||||||
pipelineCreateInfo.pViewportState = &viewportState;
|
|
||||||
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
|
|
||||||
pipelineCreateInfo.pDynamicState = &dynamicState;
|
|
||||||
pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
|
|
||||||
pipelineCreateInfo.pStages = shaderStages.data();
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkCreateGraphicsPipelines(vulkanDevice->logicalDevice, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare a separate render pass for rendering the text as an overlay
|
|
||||||
*/
|
|
||||||
void prepareRenderPass()
|
|
||||||
{
|
|
||||||
VkAttachmentDescription attachments[2] = {};
|
|
||||||
|
|
||||||
// Color attachment
|
|
||||||
attachments[0].format = colorFormat;
|
|
||||||
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
// Don't clear the framebuffer (like the renderpass from the example does)
|
|
||||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
|
||||||
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
||||||
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
||||||
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
||||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
||||||
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
||||||
|
|
||||||
// Depth attachment
|
|
||||||
attachments[1].format = depthFormat;
|
|
||||||
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
||||||
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
||||||
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
||||||
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
||||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
||||||
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
||||||
|
|
||||||
VkAttachmentReference colorReference = {};
|
|
||||||
colorReference.attachment = 0;
|
|
||||||
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
||||||
|
|
||||||
VkAttachmentReference depthReference = {};
|
|
||||||
depthReference.attachment = 1;
|
|
||||||
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
||||||
|
|
||||||
VkSubpassDependency subpassDependencies[2] = {};
|
|
||||||
|
|
||||||
// Transition from final to initial (VK_SUBPASS_EXTERNAL refers to all commmands executed outside of the actual renderpass)
|
|
||||||
subpassDependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
|
||||||
subpassDependencies[0].dstSubpass = 0;
|
|
||||||
subpassDependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
||||||
subpassDependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
||||||
subpassDependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
||||||
subpassDependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
||||||
subpassDependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
|
||||||
|
|
||||||
// Transition from initial to final
|
|
||||||
subpassDependencies[1].srcSubpass = 0;
|
|
||||||
subpassDependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
|
||||||
subpassDependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
||||||
subpassDependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
||||||
subpassDependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
||||||
subpassDependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
||||||
subpassDependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
|
||||||
|
|
||||||
VkSubpassDescription subpassDescription = {};
|
|
||||||
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
||||||
subpassDescription.flags = 0;
|
|
||||||
subpassDescription.inputAttachmentCount = 0;
|
|
||||||
subpassDescription.pInputAttachments = NULL;
|
|
||||||
subpassDescription.colorAttachmentCount = 1;
|
|
||||||
subpassDescription.pColorAttachments = &colorReference;
|
|
||||||
subpassDescription.pResolveAttachments = NULL;
|
|
||||||
subpassDescription.pDepthStencilAttachment = &depthReference;
|
|
||||||
subpassDescription.preserveAttachmentCount = 0;
|
|
||||||
subpassDescription.pPreserveAttachments = NULL;
|
|
||||||
|
|
||||||
VkRenderPassCreateInfo renderPassInfo = {};
|
|
||||||
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
|
||||||
renderPassInfo.pNext = NULL;
|
|
||||||
renderPassInfo.attachmentCount = 2;
|
|
||||||
renderPassInfo.pAttachments = attachments;
|
|
||||||
renderPassInfo.subpassCount = 1;
|
|
||||||
renderPassInfo.pSubpasses = &subpassDescription;
|
|
||||||
renderPassInfo.dependencyCount = 2;
|
|
||||||
renderPassInfo.pDependencies = subpassDependencies;
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkCreateRenderPass(vulkanDevice->logicalDevice, &renderPassInfo, nullptr, &renderPass));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps the buffer, resets letter count
|
|
||||||
*/
|
|
||||||
void beginTextUpdate()
|
|
||||||
{
|
|
||||||
mappedLocal = (glm::vec4*)vertexBuffer.mapped;
|
|
||||||
numLetters = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add text to the current buffer
|
|
||||||
*
|
|
||||||
* @param text Text to add
|
|
||||||
* @param x x position of the text to add in window coordinate space
|
|
||||||
* @param y y position of the text to add in window coordinate space
|
|
||||||
* @param align Alignment for the new text (left, right, center)
|
|
||||||
*/
|
|
||||||
void addText(std::string text, float x, float y, TextAlign align)
|
|
||||||
{
|
|
||||||
assert(vertexBuffer.mapped != nullptr);
|
|
||||||
|
|
||||||
if (align == alignLeft) {
|
|
||||||
x *= scale;
|
|
||||||
};
|
|
||||||
|
|
||||||
y *= scale;
|
|
||||||
|
|
||||||
const float charW = (1.5f * scale) / *frameBufferWidth;
|
|
||||||
const float charH = (1.5f * scale) / *frameBufferHeight;
|
|
||||||
|
|
||||||
float fbW = (float)*frameBufferWidth;
|
|
||||||
float fbH = (float)*frameBufferHeight;
|
|
||||||
x = (x / fbW * 2.0f) - 1.0f;
|
|
||||||
y = (y / fbH * 2.0f) - 1.0f;
|
|
||||||
|
|
||||||
// Calculate text width
|
|
||||||
float textWidth = 0;
|
|
||||||
for (auto letter : text)
|
|
||||||
{
|
|
||||||
stb_fontchar *charData = &stbFontData[(uint32_t)letter - STB_FIRST_CHAR];
|
|
||||||
textWidth += charData->advance * charW;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (align)
|
|
||||||
{
|
|
||||||
case alignRight:
|
|
||||||
x -= textWidth;
|
|
||||||
break;
|
|
||||||
case alignCenter:
|
|
||||||
x -= textWidth / 2.0f;
|
|
||||||
break;
|
|
||||||
case alignLeft:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a uv mapped quad per char in the new text
|
|
||||||
for (auto letter : text)
|
|
||||||
{
|
|
||||||
stb_fontchar *charData = &stbFontData[(uint32_t)letter - STB_FIRST_CHAR];
|
|
||||||
|
|
||||||
mappedLocal->x = (x + (float)charData->x0 * charW);
|
|
||||||
mappedLocal->y = (y + (float)charData->y0 * charH);
|
|
||||||
mappedLocal->z = charData->s0;
|
|
||||||
mappedLocal->w = charData->t0;
|
|
||||||
mappedLocal++;
|
|
||||||
|
|
||||||
mappedLocal->x = (x + (float)charData->x1 * charW);
|
|
||||||
mappedLocal->y = (y + (float)charData->y0 * charH);
|
|
||||||
mappedLocal->z = charData->s1;
|
|
||||||
mappedLocal->w = charData->t0;
|
|
||||||
mappedLocal++;
|
|
||||||
|
|
||||||
mappedLocal->x = (x + (float)charData->x0 * charW);
|
|
||||||
mappedLocal->y = (y + (float)charData->y1 * charH);
|
|
||||||
mappedLocal->z = charData->s0;
|
|
||||||
mappedLocal->w = charData->t1;
|
|
||||||
mappedLocal++;
|
|
||||||
|
|
||||||
mappedLocal->x = (x + (float)charData->x1 * charW);
|
|
||||||
mappedLocal->y = (y + (float)charData->y1 * charH);
|
|
||||||
mappedLocal->z = charData->s1;
|
|
||||||
mappedLocal->w = charData->t1;
|
|
||||||
mappedLocal++;
|
|
||||||
|
|
||||||
x += charData->advance * charW;
|
|
||||||
|
|
||||||
numLetters++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unmap buffer and update command buffers
|
|
||||||
*/
|
|
||||||
void endTextUpdate()
|
|
||||||
{
|
|
||||||
updateCommandBuffers();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the command buffers to reflect text changes
|
|
||||||
*/
|
|
||||||
void updateCommandBuffers()
|
|
||||||
{
|
|
||||||
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
|
|
||||||
|
|
||||||
VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
|
|
||||||
renderPassBeginInfo.renderPass = renderPass;
|
|
||||||
renderPassBeginInfo.renderArea.extent.width = *frameBufferWidth;
|
|
||||||
renderPassBeginInfo.renderArea.extent.height = *frameBufferHeight;
|
|
||||||
// None of the attachments will be cleared
|
|
||||||
renderPassBeginInfo.clearValueCount = 0;
|
|
||||||
renderPassBeginInfo.pClearValues = nullptr;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < cmdBuffers.size(); ++i)
|
|
||||||
{
|
|
||||||
renderPassBeginInfo.framebuffer = *frameBuffers[i];
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffers[i], &cmdBufInfo));
|
|
||||||
|
|
||||||
if (vks::debugmarker::active)
|
|
||||||
{
|
|
||||||
vks::debugmarker::beginRegion(cmdBuffers[i], "Text overlay", glm::vec4(1.0f, 0.94f, 0.3f, 1.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
vkCmdBeginRenderPass(cmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
|
||||||
|
|
||||||
VkViewport viewport = vks::initializers::viewport((float)*frameBufferWidth, (float)*frameBufferHeight, 0.0f, 1.0f);
|
|
||||||
vkCmdSetViewport(cmdBuffers[i], 0, 1, &viewport);
|
|
||||||
|
|
||||||
VkRect2D scissor = vks::initializers::rect2D(*frameBufferWidth, *frameBufferHeight, 0, 0);
|
|
||||||
vkCmdSetScissor(cmdBuffers[i], 0, 1, &scissor);
|
|
||||||
|
|
||||||
vkCmdBindPipeline(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
|
||||||
vkCmdBindDescriptorSets(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
|
|
||||||
|
|
||||||
VkDeviceSize offsets = 0;
|
|
||||||
vkCmdBindVertexBuffers(cmdBuffers[i], 0, 1, &vertexBuffer.buffer, &offsets);
|
|
||||||
vkCmdBindVertexBuffers(cmdBuffers[i], 1, 1, &vertexBuffer.buffer, &offsets);
|
|
||||||
for (uint32_t j = 0; j < numLetters; j++)
|
|
||||||
{
|
|
||||||
vkCmdDraw(cmdBuffers[i], 4, 1, j * 4, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
vkCmdEndRenderPass(cmdBuffers[i]);
|
|
||||||
|
|
||||||
if (vks::debugmarker::active)
|
|
||||||
{
|
|
||||||
vks::debugmarker::endRegion(cmdBuffers[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuffers[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submit the text command buffers to a queue
|
|
||||||
*/
|
|
||||||
void submit(VkQueue queue, uint32_t bufferindex, VkSubmitInfo submitInfo)
|
|
||||||
{
|
|
||||||
if (!visible)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
submitInfo.pCommandBuffers = &cmdBuffers[bufferindex];
|
|
||||||
submitInfo.commandBufferCount = 1;
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence));
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkWaitForFences(vulkanDevice->logicalDevice, 1, &fence, VK_TRUE, UINT64_MAX));
|
|
||||||
VK_CHECK_RESULT(vkResetFences(vulkanDevice->logicalDevice, 1, &fence));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reallocate command buffers for the text overlay
|
|
||||||
* @note Frees the existing command buffers
|
|
||||||
*/
|
|
||||||
void reallocateCommandBuffers()
|
|
||||||
{
|
|
||||||
vkFreeCommandBuffers(vulkanDevice->logicalDevice, commandPool, static_cast<uint32_t>(cmdBuffers.size()), cmdBuffers.data());
|
|
||||||
|
|
||||||
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
|
|
||||||
vks::initializers::commandBufferAllocateInfo(
|
|
||||||
commandPool,
|
|
||||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
|
||||||
static_cast<uint32_t>(cmdBuffers.size()));
|
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(vulkanDevice->logicalDevice, &cmdBufAllocateInfo, cmdBuffers.data()));
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
555
base/VulkanUIOverlay.cpp
Normal file
555
base/VulkanUIOverlay.cpp
Normal file
|
|
@ -0,0 +1,555 @@
|
||||||
|
/*
|
||||||
|
* UI overlay class using ImGui
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 by Sascha Willems - www.saschawillems.de
|
||||||
|
*
|
||||||
|
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "VulkanUIOverlay.h"
|
||||||
|
|
||||||
|
namespace vks
|
||||||
|
{
|
||||||
|
UIOverlay::UIOverlay(vks::VulkanDevice *vulkanDevice, VkQueue copyQueue, std::vector<VkFramebuffer> &framebuffers, VkFormat colorformat, VkFormat depthformat, uint32_t *framebufferwidth, uint32_t *framebufferheight, std::vector<VkPipelineShaderStageCreateInfo> shaderstages)
|
||||||
|
{
|
||||||
|
this->device = vulkanDevice;
|
||||||
|
this->copyQueue = copyQueue;
|
||||||
|
this->colorFormat = colorformat;
|
||||||
|
this->depthFormat = depthformat;
|
||||||
|
|
||||||
|
this->frameBuffers.resize(framebuffers.size());
|
||||||
|
for (uint32_t i = 0; i < framebuffers.size(); i++) {
|
||||||
|
this->frameBuffers[i] = &framebuffers[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
this->shaderStages = shaderstages;
|
||||||
|
|
||||||
|
this->frameBufferWidth = framebufferwidth;
|
||||||
|
this->frameBufferHeight = framebufferheight;
|
||||||
|
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
// Scale text on Android devices with high DPI
|
||||||
|
if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XXHIGH) {
|
||||||
|
scale = 2.0f;
|
||||||
|
}
|
||||||
|
else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XHIGH) {
|
||||||
|
scale = 1.5f;
|
||||||
|
}
|
||||||
|
else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_HIGH) {
|
||||||
|
scale = 1.25f;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Init ImGui
|
||||||
|
// Color scheme
|
||||||
|
ImGuiStyle& style = ImGui::GetStyle();
|
||||||
|
style.Colors[ImGuiCol_TitleBg] = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
style.Colors[ImGuiCol_TitleBgActive] = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.0f, 0.0f, 0.0f, 0.1f);
|
||||||
|
style.Colors[ImGuiCol_MenuBarBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
||||||
|
style.Colors[ImGuiCol_Header] = ImVec4(0.8f, 0.0f, 0.0f, 0.4f);
|
||||||
|
style.Colors[ImGuiCol_HeaderActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
||||||
|
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
|
||||||
|
style.Colors[ImGuiCol_CheckMark] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
|
||||||
|
// Dimensions
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
io.DisplaySize = ImVec2((float)(*framebufferwidth), (float)(*framebufferheight));
|
||||||
|
io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
|
||||||
|
|
||||||
|
cmdBuffers.resize(framebuffers.size());
|
||||||
|
prepareResources();
|
||||||
|
prepareRenderPass();
|
||||||
|
preparePipeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Free up all Vulkan resources acquired by the UI overlay */
|
||||||
|
UIOverlay::~UIOverlay()
|
||||||
|
{
|
||||||
|
vertexBuffer.destroy();
|
||||||
|
vkDestroySampler(device->logicalDevice, sampler, nullptr);
|
||||||
|
vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayout, nullptr);
|
||||||
|
vkDestroyDescriptorPool(device->logicalDevice, descriptorPool, nullptr);
|
||||||
|
vkDestroyPipelineLayout(device->logicalDevice, pipelineLayout, nullptr);
|
||||||
|
vkDestroyPipelineCache(device->logicalDevice, pipelineCache, nullptr);
|
||||||
|
vkDestroyPipeline(device->logicalDevice, pipeline, nullptr);
|
||||||
|
vkDestroyRenderPass(device->logicalDevice, renderPass, nullptr);
|
||||||
|
vkFreeCommandBuffers(device->logicalDevice, commandPool, static_cast<uint32_t>(cmdBuffers.size()), cmdBuffers.data());
|
||||||
|
vkDestroyCommandPool(device->logicalDevice, commandPool, nullptr);
|
||||||
|
vkDestroyFence(device->logicalDevice, fence, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prepare all vulkan resources required to render the UI overlay */
|
||||||
|
void UIOverlay::prepareResources()
|
||||||
|
{
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
|
||||||
|
// Create font texture
|
||||||
|
unsigned char* fontData;
|
||||||
|
int texWidth, texHeight;
|
||||||
|
io.Fonts->GetTexDataAsRGBA32(&fontData, &texWidth, &texHeight);
|
||||||
|
VkDeviceSize uploadSize = texWidth*texHeight * 4 * sizeof(char);
|
||||||
|
|
||||||
|
// Create target image for copy
|
||||||
|
VkImageCreateInfo imageInfo = vks::initializers::imageCreateInfo();
|
||||||
|
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||||
|
imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
imageInfo.extent.width = texWidth;
|
||||||
|
imageInfo.extent.height = texHeight;
|
||||||
|
imageInfo.extent.depth = 1;
|
||||||
|
imageInfo.mipLevels = 1;
|
||||||
|
imageInfo.arrayLayers = 1;
|
||||||
|
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||||
|
imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||||
|
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
|
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageInfo, nullptr, &fontImage));
|
||||||
|
VkMemoryRequirements memReqs;
|
||||||
|
vkGetImageMemoryRequirements(device->logicalDevice, fontImage, &memReqs);
|
||||||
|
VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
|
||||||
|
memAllocInfo.allocationSize = memReqs.size;
|
||||||
|
memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||||
|
VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &fontMemory));
|
||||||
|
VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, fontImage, fontMemory, 0));
|
||||||
|
|
||||||
|
// Image view
|
||||||
|
VkImageViewCreateInfo viewInfo = vks::initializers::imageViewCreateInfo();
|
||||||
|
viewInfo.image = fontImage;
|
||||||
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
viewInfo.subresourceRange.levelCount = 1;
|
||||||
|
viewInfo.subresourceRange.layerCount = 1;
|
||||||
|
VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewInfo, nullptr, &fontView));
|
||||||
|
|
||||||
|
// Staging buffers for font data upload
|
||||||
|
vks::Buffer stagingBuffer;
|
||||||
|
|
||||||
|
VK_CHECK_RESULT(device->createBuffer(
|
||||||
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||||
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||||
|
&stagingBuffer,
|
||||||
|
uploadSize));
|
||||||
|
|
||||||
|
stagingBuffer.map();
|
||||||
|
memcpy(stagingBuffer.mapped, fontData, uploadSize);
|
||||||
|
stagingBuffer.unmap();
|
||||||
|
|
||||||
|
// Copy buffer data to font image
|
||||||
|
VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||||
|
|
||||||
|
// Prepare for transfer
|
||||||
|
vks::tools::setImageLayout(
|
||||||
|
copyCmd,
|
||||||
|
fontImage,
|
||||||
|
VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
|
VK_PIPELINE_STAGE_HOST_BIT,
|
||||||
|
VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||||
|
|
||||||
|
// Copy
|
||||||
|
VkBufferImageCopy bufferCopyRegion = {};
|
||||||
|
bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
bufferCopyRegion.imageSubresource.layerCount = 1;
|
||||||
|
bufferCopyRegion.imageExtent.width = texWidth;
|
||||||
|
bufferCopyRegion.imageExtent.height = texHeight;
|
||||||
|
bufferCopyRegion.imageExtent.depth = 1;
|
||||||
|
|
||||||
|
vkCmdCopyBufferToImage(
|
||||||
|
copyCmd,
|
||||||
|
stagingBuffer.buffer,
|
||||||
|
fontImage,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
|
1,
|
||||||
|
&bufferCopyRegion
|
||||||
|
);
|
||||||
|
|
||||||
|
// Prepare for shader read
|
||||||
|
vks::tools::setImageLayout(
|
||||||
|
copyCmd,
|
||||||
|
fontImage,
|
||||||
|
VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||||
|
|
||||||
|
device->flushCommandBuffer(copyCmd, copyQueue, true);
|
||||||
|
|
||||||
|
stagingBuffer.destroy();
|
||||||
|
|
||||||
|
// Font texture Sampler
|
||||||
|
VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
|
||||||
|
samplerInfo.magFilter = VK_FILTER_LINEAR;
|
||||||
|
samplerInfo.minFilter = VK_FILTER_LINEAR;
|
||||||
|
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||||
|
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||||
|
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||||
|
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||||
|
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||||
|
VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerInfo, nullptr, &sampler));
|
||||||
|
|
||||||
|
// Command buffer
|
||||||
|
VkCommandPoolCreateInfo cmdPoolInfo = {};
|
||||||
|
cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||||
|
cmdPoolInfo.queueFamilyIndex = device->queueFamilyIndices.graphics;
|
||||||
|
cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||||
|
VK_CHECK_RESULT(vkCreateCommandPool(device->logicalDevice, &cmdPoolInfo, nullptr, &commandPool));
|
||||||
|
|
||||||
|
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
|
||||||
|
vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast<uint32_t>(cmdBuffers.size()));
|
||||||
|
VK_CHECK_RESULT(vkAllocateCommandBuffers(device->logicalDevice, &cmdBufAllocateInfo, cmdBuffers.data()));
|
||||||
|
|
||||||
|
// Descriptor pool
|
||||||
|
std::vector<VkDescriptorPoolSize> poolSizes = {
|
||||||
|
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
|
||||||
|
};
|
||||||
|
VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
|
||||||
|
VK_CHECK_RESULT(vkCreateDescriptorPool(device->logicalDevice, &descriptorPoolInfo, nullptr, &descriptorPool));
|
||||||
|
|
||||||
|
// Descriptor set layout
|
||||||
|
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
|
||||||
|
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
|
||||||
|
};
|
||||||
|
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
|
||||||
|
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device->logicalDevice, &descriptorLayout, nullptr, &descriptorSetLayout));
|
||||||
|
|
||||||
|
// Descriptor set
|
||||||
|
VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
|
||||||
|
VK_CHECK_RESULT(vkAllocateDescriptorSets(device->logicalDevice, &allocInfo, &descriptorSet));
|
||||||
|
VkDescriptorImageInfo fontDescriptor = vks::initializers::descriptorImageInfo(
|
||||||
|
sampler,
|
||||||
|
fontView,
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
|
||||||
|
);
|
||||||
|
std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
|
||||||
|
vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &fontDescriptor)
|
||||||
|
};
|
||||||
|
vkUpdateDescriptorSets(device->logicalDevice, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
|
||||||
|
|
||||||
|
// Pipeline cache
|
||||||
|
VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
|
||||||
|
pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
|
||||||
|
VK_CHECK_RESULT(vkCreatePipelineCache(device->logicalDevice, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
|
||||||
|
|
||||||
|
// Pipeline layout
|
||||||
|
// Push constants for UI rendering parameters
|
||||||
|
VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstBlock), 0);
|
||||||
|
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
|
||||||
|
pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
|
||||||
|
pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
|
||||||
|
VK_CHECK_RESULT(vkCreatePipelineLayout(device->logicalDevice, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
|
||||||
|
|
||||||
|
// Command buffer execution fence
|
||||||
|
VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo();
|
||||||
|
VK_CHECK_RESULT(vkCreateFence(device->logicalDevice, &fenceCreateInfo, nullptr, &fence));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prepare a separate pipeline for the UI overlay rendering decoupled from the main application */
|
||||||
|
void UIOverlay::preparePipeline()
|
||||||
|
{
|
||||||
|
// Setup graphics pipeline for UI rendering
|
||||||
|
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
|
||||||
|
vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
|
||||||
|
|
||||||
|
VkPipelineRasterizationStateCreateInfo rasterizationState =
|
||||||
|
vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
|
||||||
|
|
||||||
|
// Enable blending
|
||||||
|
VkPipelineColorBlendAttachmentState blendAttachmentState{};
|
||||||
|
blendAttachmentState.blendEnable = VK_TRUE;
|
||||||
|
blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||||
|
blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||||
|
blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||||
|
blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
|
||||||
|
blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||||
|
blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||||
|
blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||||
|
|
||||||
|
VkPipelineColorBlendStateCreateInfo colorBlendState =
|
||||||
|
vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
|
||||||
|
|
||||||
|
VkPipelineDepthStencilStateCreateInfo depthStencilState =
|
||||||
|
vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
|
||||||
|
|
||||||
|
VkPipelineViewportStateCreateInfo viewportState =
|
||||||
|
vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
|
||||||
|
|
||||||
|
VkPipelineMultisampleStateCreateInfo multisampleState =
|
||||||
|
vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
|
||||||
|
|
||||||
|
std::vector<VkDynamicState> dynamicStateEnables = {
|
||||||
|
VK_DYNAMIC_STATE_VIEWPORT,
|
||||||
|
VK_DYNAMIC_STATE_SCISSOR
|
||||||
|
};
|
||||||
|
VkPipelineDynamicStateCreateInfo dynamicState =
|
||||||
|
vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
|
||||||
|
|
||||||
|
VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
|
||||||
|
|
||||||
|
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
|
||||||
|
pipelineCreateInfo.pRasterizationState = &rasterizationState;
|
||||||
|
pipelineCreateInfo.pColorBlendState = &colorBlendState;
|
||||||
|
pipelineCreateInfo.pMultisampleState = &multisampleState;
|
||||||
|
pipelineCreateInfo.pViewportState = &viewportState;
|
||||||
|
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
|
||||||
|
pipelineCreateInfo.pDynamicState = &dynamicState;
|
||||||
|
pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
|
||||||
|
pipelineCreateInfo.pStages = shaderStages.data();
|
||||||
|
|
||||||
|
// Vertex bindings an attributes based on ImGui vertex definition
|
||||||
|
std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
|
||||||
|
vks::initializers::vertexInputBindingDescription(0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX),
|
||||||
|
};
|
||||||
|
std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
|
||||||
|
vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, pos)), // Location 0: Position
|
||||||
|
vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, uv)), // Location 1: UV
|
||||||
|
vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R8G8B8A8_UNORM, offsetof(ImDrawVert, col)), // Location 0: Color
|
||||||
|
};
|
||||||
|
VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
|
||||||
|
vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
|
||||||
|
vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
|
||||||
|
vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
|
||||||
|
vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
|
||||||
|
|
||||||
|
pipelineCreateInfo.pVertexInputState = &vertexInputState;
|
||||||
|
|
||||||
|
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device->logicalDevice, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prepare a separate render pass for rendering the text as an overlay */
|
||||||
|
void UIOverlay::prepareRenderPass()
|
||||||
|
{
|
||||||
|
VkAttachmentDescription attachments[2] = {};
|
||||||
|
|
||||||
|
// Color attachment
|
||||||
|
attachments[0].format = colorFormat;
|
||||||
|
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
// Don't clear the framebuffer (like the renderpass from the example does)
|
||||||
|
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||||
|
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
|
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
|
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
|
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||||
|
|
||||||
|
// Depth attachment
|
||||||
|
attachments[1].format = depthFormat;
|
||||||
|
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
|
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
|
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
|
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
|
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||||
|
|
||||||
|
VkAttachmentReference colorReference = {};
|
||||||
|
colorReference.attachment = 0;
|
||||||
|
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||||
|
|
||||||
|
VkAttachmentReference depthReference = {};
|
||||||
|
depthReference.attachment = 1;
|
||||||
|
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||||
|
|
||||||
|
VkSubpassDependency subpassDependencies[2] = {};
|
||||||
|
|
||||||
|
// Transition from final to initial (VK_SUBPASS_EXTERNAL refers to all commmands executed outside of the actual renderpass)
|
||||||
|
subpassDependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||||
|
subpassDependencies[0].dstSubpass = 0;
|
||||||
|
subpassDependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||||
|
subpassDependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
subpassDependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||||
|
subpassDependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||||
|
subpassDependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||||
|
|
||||||
|
// Transition from initial to final
|
||||||
|
subpassDependencies[1].srcSubpass = 0;
|
||||||
|
subpassDependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||||
|
subpassDependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
subpassDependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||||
|
subpassDependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||||
|
subpassDependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||||
|
subpassDependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||||
|
|
||||||
|
VkSubpassDescription subpassDescription = {};
|
||||||
|
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||||
|
subpassDescription.flags = 0;
|
||||||
|
subpassDescription.inputAttachmentCount = 0;
|
||||||
|
subpassDescription.pInputAttachments = NULL;
|
||||||
|
subpassDescription.colorAttachmentCount = 1;
|
||||||
|
subpassDescription.pColorAttachments = &colorReference;
|
||||||
|
subpassDescription.pResolveAttachments = NULL;
|
||||||
|
subpassDescription.pDepthStencilAttachment = &depthReference;
|
||||||
|
subpassDescription.preserveAttachmentCount = 0;
|
||||||
|
subpassDescription.pPreserveAttachments = NULL;
|
||||||
|
|
||||||
|
VkRenderPassCreateInfo renderPassInfo = {};
|
||||||
|
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||||
|
renderPassInfo.pNext = NULL;
|
||||||
|
renderPassInfo.attachmentCount = 2;
|
||||||
|
renderPassInfo.pAttachments = attachments;
|
||||||
|
renderPassInfo.subpassCount = 1;
|
||||||
|
renderPassInfo.pSubpasses = &subpassDescription;
|
||||||
|
renderPassInfo.dependencyCount = 2;
|
||||||
|
renderPassInfo.pDependencies = subpassDependencies;
|
||||||
|
|
||||||
|
VK_CHECK_RESULT(vkCreateRenderPass(device->logicalDevice, &renderPassInfo, nullptr, &renderPass));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the command buffers to reflect text changes */
|
||||||
|
void UIOverlay::updateCommandBuffers()
|
||||||
|
{
|
||||||
|
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
|
||||||
|
|
||||||
|
VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
|
||||||
|
renderPassBeginInfo.renderPass = renderPass;
|
||||||
|
renderPassBeginInfo.renderArea.extent.width = *frameBufferWidth;
|
||||||
|
renderPassBeginInfo.renderArea.extent.height = *frameBufferHeight;
|
||||||
|
// None of the attachments will be cleared
|
||||||
|
renderPassBeginInfo.clearValueCount = 0;
|
||||||
|
renderPassBeginInfo.pClearValues = nullptr;
|
||||||
|
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < cmdBuffers.size(); ++i) {
|
||||||
|
renderPassBeginInfo.framebuffer = *frameBuffers[i];
|
||||||
|
|
||||||
|
VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffers[i], &cmdBufInfo));
|
||||||
|
|
||||||
|
if (vks::debugmarker::active) {
|
||||||
|
vks::debugmarker::beginRegion(cmdBuffers[i], "UI overlay", glm::vec4(1.0f, 0.94f, 0.3f, 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
vkCmdBeginRenderPass(cmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
|
vkCmdBindPipeline(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
|
vkCmdBindDescriptorSets(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
|
||||||
|
|
||||||
|
VkDeviceSize offsets[1] = { 0 };
|
||||||
|
vkCmdBindVertexBuffers(cmdBuffers[i], 0, 1, &vertexBuffer.buffer, offsets);
|
||||||
|
vkCmdBindIndexBuffer(cmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT16);
|
||||||
|
|
||||||
|
VkViewport viewport = vks::initializers::viewport(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, 1.0f);
|
||||||
|
vkCmdSetViewport(cmdBuffers[i], 0, 1, &viewport);
|
||||||
|
|
||||||
|
VkRect2D scissor = vks::initializers::rect2D((int32_t)ImGui::GetIO().DisplaySize.x, (int32_t)ImGui::GetIO().DisplaySize.y, 0, 0);
|
||||||
|
vkCmdSetScissor(cmdBuffers[i], 0, 1, &scissor);
|
||||||
|
|
||||||
|
// UI scale and translate via push constants
|
||||||
|
pushConstBlock.scale = glm::vec2(2.0f / io.DisplaySize.x, 2.0f / io.DisplaySize.y);
|
||||||
|
pushConstBlock.translate = glm::vec2(-1.0f);
|
||||||
|
vkCmdPushConstants(cmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
|
||||||
|
|
||||||
|
// Render commands
|
||||||
|
ImDrawData* imDrawData = ImGui::GetDrawData();
|
||||||
|
int32_t vertexOffset = 0;
|
||||||
|
int32_t indexOffset = 0;
|
||||||
|
for (int32_t j = 0; j < imDrawData->CmdListsCount; j++) {
|
||||||
|
const ImDrawList* cmd_list = imDrawData->CmdLists[j];
|
||||||
|
for (int32_t k = 0; k < cmd_list->CmdBuffer.Size; k++) {
|
||||||
|
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[k];
|
||||||
|
VkRect2D scissorRect;
|
||||||
|
scissorRect.offset.x = std::max((int32_t)(pcmd->ClipRect.x), 0);
|
||||||
|
scissorRect.offset.y = std::max((int32_t)(pcmd->ClipRect.y), 0);
|
||||||
|
scissorRect.extent.width = (uint32_t)(pcmd->ClipRect.z - pcmd->ClipRect.x);
|
||||||
|
scissorRect.extent.height = (uint32_t)(pcmd->ClipRect.w - pcmd->ClipRect.y);
|
||||||
|
vkCmdSetScissor(cmdBuffers[i], 0, 1, &scissorRect);
|
||||||
|
vkCmdDrawIndexed(cmdBuffers[i], pcmd->ElemCount, 1, indexOffset, vertexOffset, 0);
|
||||||
|
indexOffset += pcmd->ElemCount;
|
||||||
|
}
|
||||||
|
vertexOffset += cmd_list->VtxBuffer.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
vkCmdEndRenderPass(cmdBuffers[i]);
|
||||||
|
|
||||||
|
if (vks::debugmarker::active) {
|
||||||
|
vks::debugmarker::endRegion(cmdBuffers[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuffers[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update vertex and index buffer containing the imGui elements when required */
|
||||||
|
void UIOverlay::update()
|
||||||
|
{
|
||||||
|
ImDrawData* imDrawData = ImGui::GetDrawData();
|
||||||
|
bool updateCmdBuffers = false;
|
||||||
|
|
||||||
|
if (!imDrawData) { return; };
|
||||||
|
|
||||||
|
// Note: Alignment is done inside buffer creation
|
||||||
|
VkDeviceSize vertexBufferSize = imDrawData->TotalVtxCount * sizeof(ImDrawVert);
|
||||||
|
VkDeviceSize indexBufferSize = imDrawData->TotalIdxCount * sizeof(ImDrawIdx);
|
||||||
|
|
||||||
|
// Update buffers only if vertex or index count has been changed compared to current buffer size
|
||||||
|
|
||||||
|
// Vertex buffer
|
||||||
|
if ((vertexBuffer.buffer == VK_NULL_HANDLE) || (vertexCount != imDrawData->TotalVtxCount)) {
|
||||||
|
vertexBuffer.unmap();
|
||||||
|
vertexBuffer.destroy();
|
||||||
|
VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &vertexBuffer, vertexBufferSize));
|
||||||
|
vertexCount = imDrawData->TotalVtxCount;
|
||||||
|
vertexBuffer.unmap();
|
||||||
|
vertexBuffer.map();
|
||||||
|
updateCmdBuffers = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index buffer
|
||||||
|
VkDeviceSize indexSize = imDrawData->TotalIdxCount * sizeof(ImDrawIdx);
|
||||||
|
if ((indexBuffer.buffer == VK_NULL_HANDLE) || (indexCount < imDrawData->TotalIdxCount)) {
|
||||||
|
indexBuffer.unmap();
|
||||||
|
indexBuffer.destroy();
|
||||||
|
VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &indexBuffer, indexBufferSize));
|
||||||
|
indexCount = imDrawData->TotalIdxCount;
|
||||||
|
indexBuffer.map();
|
||||||
|
updateCmdBuffers = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload data
|
||||||
|
ImDrawVert* vtxDst = (ImDrawVert*)vertexBuffer.mapped;
|
||||||
|
ImDrawIdx* idxDst = (ImDrawIdx*)indexBuffer.mapped;
|
||||||
|
|
||||||
|
for (int n = 0; n < imDrawData->CmdListsCount; n++) {
|
||||||
|
const ImDrawList* cmd_list = imDrawData->CmdLists[n];
|
||||||
|
memcpy(vtxDst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
|
||||||
|
memcpy(idxDst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||||
|
vtxDst += cmd_list->VtxBuffer.Size;
|
||||||
|
idxDst += cmd_list->IdxBuffer.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush to make writes visible to GPU
|
||||||
|
vertexBuffer.flush();
|
||||||
|
indexBuffer.flush();
|
||||||
|
|
||||||
|
if (updateCmdBuffers) {
|
||||||
|
updateCommandBuffers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Submit the overlay command buffers to a queue */
|
||||||
|
void UIOverlay::submit(VkQueue queue, uint32_t bufferindex, VkSubmitInfo submitInfo)
|
||||||
|
{
|
||||||
|
if (!visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitInfo.pCommandBuffers = &cmdBuffers[bufferindex];
|
||||||
|
submitInfo.commandBufferCount = 1;
|
||||||
|
|
||||||
|
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence));
|
||||||
|
|
||||||
|
VK_CHECK_RESULT(vkWaitForFences(device->logicalDevice, 1, &fence, VK_TRUE, UINT64_MAX));
|
||||||
|
VK_CHECK_RESULT(vkResetFences(device->logicalDevice, 1, &fence));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reallocate command buffers for the text overlay */
|
||||||
|
void UIOverlay::reallocateCommandBuffers()
|
||||||
|
{
|
||||||
|
vkFreeCommandBuffers(device->logicalDevice, commandPool, static_cast<uint32_t>(cmdBuffers.size()), cmdBuffers.data());
|
||||||
|
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
|
||||||
|
vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast<uint32_t>(cmdBuffers.size()));
|
||||||
|
VK_CHECK_RESULT(vkAllocateCommandBuffers(device->logicalDevice, &cmdBufAllocateInfo, cmdBuffers.data()));
|
||||||
|
}
|
||||||
|
}
|
||||||
90
base/VulkanUIOverlay.h
Normal file
90
base/VulkanUIOverlay.h
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* UI overlay class using ImGui
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 by Sascha Willems - www.saschawillems.de
|
||||||
|
*
|
||||||
|
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include "VulkanTools.h"
|
||||||
|
#include "VulkanDebug.h"
|
||||||
|
#include "VulkanBuffer.hpp"
|
||||||
|
#include "VulkanDevice.hpp"
|
||||||
|
|
||||||
|
#include "../external/imgui/imgui.h"
|
||||||
|
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
#include "VulkanAndroid.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace vks
|
||||||
|
{
|
||||||
|
class UIOverlay
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
vks::VulkanDevice *device;
|
||||||
|
|
||||||
|
VkQueue copyQueue;
|
||||||
|
VkFormat colorFormat;
|
||||||
|
VkFormat depthFormat;
|
||||||
|
|
||||||
|
uint32_t *frameBufferWidth;
|
||||||
|
uint32_t *frameBufferHeight;
|
||||||
|
|
||||||
|
vks::Buffer vertexBuffer;
|
||||||
|
vks::Buffer indexBuffer;
|
||||||
|
int32_t vertexCount = 0;
|
||||||
|
int32_t indexCount = 0;
|
||||||
|
|
||||||
|
VkDescriptorPool descriptorPool;
|
||||||
|
VkDescriptorSetLayout descriptorSetLayout;
|
||||||
|
VkDescriptorSet descriptorSet;
|
||||||
|
VkPipelineLayout pipelineLayout;
|
||||||
|
VkPipelineCache pipelineCache;
|
||||||
|
VkPipeline pipeline;
|
||||||
|
VkRenderPass renderPass;
|
||||||
|
VkCommandPool commandPool;
|
||||||
|
std::vector<VkFramebuffer*> frameBuffers;
|
||||||
|
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
|
||||||
|
VkFence fence;
|
||||||
|
|
||||||
|
VkDeviceMemory fontMemory = VK_NULL_HANDLE;
|
||||||
|
VkImage fontImage = VK_NULL_HANDLE;
|
||||||
|
VkImageView fontView = VK_NULL_HANDLE;
|
||||||
|
VkSampler sampler;
|
||||||
|
|
||||||
|
struct PushConstBlock {
|
||||||
|
glm::vec2 scale;
|
||||||
|
glm::vec2 translate;
|
||||||
|
} pushConstBlock;
|
||||||
|
|
||||||
|
void prepareResources();
|
||||||
|
void preparePipeline();
|
||||||
|
void prepareRenderPass();
|
||||||
|
void updateCommandBuffers();
|
||||||
|
public:
|
||||||
|
bool visible = true;
|
||||||
|
float scale = 1.0f;
|
||||||
|
|
||||||
|
std::vector<VkCommandBuffer> cmdBuffers;
|
||||||
|
|
||||||
|
UIOverlay(vks::VulkanDevice *vulkanDevice, VkQueue copyQueue, std::vector<VkFramebuffer> &framebuffers, VkFormat colorformat, VkFormat depthformat, uint32_t *framebufferwidth, uint32_t *framebufferheight, std::vector<VkPipelineShaderStageCreateInfo> shaderstages);
|
||||||
|
~UIOverlay();
|
||||||
|
|
||||||
|
void reallocateCommandBuffers();
|
||||||
|
void update();
|
||||||
|
|
||||||
|
void submit(VkQueue queue, uint32_t bufferindex, VkSubmitInfo submitInfo);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -70,8 +70,7 @@ std::string VulkanExampleBase::getWindowTitle()
|
||||||
std::string device(deviceProperties.deviceName);
|
std::string device(deviceProperties.deviceName);
|
||||||
std::string windowTitle;
|
std::string windowTitle;
|
||||||
windowTitle = title + " - " + device;
|
windowTitle = title + " - " + device;
|
||||||
if (!enableTextOverlay)
|
if (!settings.overlay) {
|
||||||
{
|
|
||||||
windowTitle += " - " + std::to_string(frameCounter) + " fps";
|
windowTitle += " - " + std::to_string(frameCounter) + " fps";
|
||||||
}
|
}
|
||||||
return windowTitle;
|
return windowTitle;
|
||||||
|
|
@ -187,24 +186,14 @@ void VulkanExampleBase::prepare()
|
||||||
setupRenderPass();
|
setupRenderPass();
|
||||||
createPipelineCache();
|
createPipelineCache();
|
||||||
setupFrameBuffer();
|
setupFrameBuffer();
|
||||||
enableTextOverlay = enableTextOverlay && (!benchmark.active);
|
settings.overlay = settings.overlay && (!benchmark.active);
|
||||||
if (enableTextOverlay)
|
if (settings.overlay) {
|
||||||
{
|
std::vector<VkPipelineShaderStageCreateInfo> shaderStages = {
|
||||||
// Load the text rendering shaders
|
loadShader(getAssetPath() + "shaders/base/uioverlay.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
|
||||||
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
|
loadShader(getAssetPath() + "shaders/base/uioverlay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT),
|
||||||
shaderStages.push_back(loadShader(getAssetPath() + "shaders/base/textoverlay.vert.spv", VK_SHADER_STAGE_VERTEX_BIT));
|
};
|
||||||
shaderStages.push_back(loadShader(getAssetPath() + "shaders/base/textoverlay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT));
|
UIOverlay = new vks::UIOverlay(vulkanDevice, queue, frameBuffers, swapChain.colorFormat, depthFormat, &width, &height, shaderStages);
|
||||||
textOverlay = new VulkanTextOverlay(
|
updateOverlay();
|
||||||
vulkanDevice,
|
|
||||||
queue,
|
|
||||||
frameBuffers,
|
|
||||||
swapChain.colorFormat,
|
|
||||||
depthFormat,
|
|
||||||
&width,
|
|
||||||
&height,
|
|
||||||
shaderStages
|
|
||||||
);
|
|
||||||
updateTextOverlay();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,16 +245,17 @@ void VulkanExampleBase::renderFrame()
|
||||||
if (fpsTimer > 1000.0f)
|
if (fpsTimer > 1000.0f)
|
||||||
{
|
{
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
if (!enableTextOverlay) {
|
if (!settings.overlay) {
|
||||||
std::string windowTitle = getWindowTitle();
|
std::string windowTitle = getWindowTitle();
|
||||||
SetWindowText(window, windowTitle.c_str());
|
SetWindowText(window, windowTitle.c_str());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
lastFPS = static_cast<uint32_t>(1.0f / frameTimer);
|
lastFPS = static_cast<uint32_t>(1.0f / frameTimer);
|
||||||
updateTextOverlay();
|
|
||||||
fpsTimer = 0.0f;
|
fpsTimer = 0.0f;
|
||||||
frameCounter = 0;
|
frameCounter = 0;
|
||||||
}
|
}
|
||||||
|
// TODO: Cap UI overlay update rates
|
||||||
|
updateOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanExampleBase::renderLoop()
|
void VulkanExampleBase::renderLoop()
|
||||||
|
|
@ -545,31 +535,41 @@ void VulkanExampleBase::renderLoop()
|
||||||
vkDeviceWaitIdle(device);
|
vkDeviceWaitIdle(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanExampleBase::updateTextOverlay()
|
void VulkanExampleBase::updateOverlay()
|
||||||
{
|
{
|
||||||
if (!enableTextOverlay)
|
if (!settings.overlay)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
textOverlay->beginTextUpdate();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
|
||||||
textOverlay->addText(title, 5.0f, 5.0f, VulkanTextOverlay::alignLeft);
|
io.DisplaySize = ImVec2((float)width, (float)height);
|
||||||
|
io.DeltaTime = frameTimer;
|
||||||
|
|
||||||
std::stringstream ss;
|
io.MousePos = ImVec2(mousePos.x, mousePos.y);
|
||||||
ss << std::fixed << std::setprecision(3) << (frameTimer * 1000.0f) << "ms (" << lastFPS << " fps)";
|
io.MouseDown[0] = mouseButtons.left;
|
||||||
textOverlay->addText(ss.str(), 5.0f, 25.0f, VulkanTextOverlay::alignLeft);
|
io.MouseDown[1] = mouseButtons.right;
|
||||||
|
|
||||||
std::string deviceName(deviceProperties.deviceName);
|
ImGui::NewFrame();
|
||||||
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
|
||||||
deviceName += " (" + androidProduct + ")";
|
|
||||||
#endif
|
|
||||||
textOverlay->addText(deviceName, 5.0f, 45.0f, VulkanTextOverlay::alignLeft);
|
|
||||||
|
|
||||||
getOverlayText(textOverlay);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(10, 10));
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiSetCond_FirstUseEver);
|
||||||
|
ImGui::Begin("Vulkan Example", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove);
|
||||||
|
ImGui::Text(title.c_str());
|
||||||
|
ImGui::Text(deviceProperties.deviceName);
|
||||||
|
ImGui::Text("%.2f ms/frame (%.1d fps)", (frameTimer * 1000.0f), lastFPS);
|
||||||
|
|
||||||
textOverlay->endTextUpdate();
|
OnUpdateUIOverlay();
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
|
ImGui::Render();
|
||||||
|
|
||||||
|
UIOverlay->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanExampleBase::getOverlayText(VulkanTextOverlay*) {}
|
//void VulkanExampleBase::getOverlayText(VulkanTextOverlay*) {}
|
||||||
|
|
||||||
void VulkanExampleBase::prepareFrame()
|
void VulkanExampleBase::prepareFrame()
|
||||||
{
|
{
|
||||||
|
|
@ -586,10 +586,9 @@ void VulkanExampleBase::prepareFrame()
|
||||||
|
|
||||||
void VulkanExampleBase::submitFrame()
|
void VulkanExampleBase::submitFrame()
|
||||||
{
|
{
|
||||||
bool submitTextOverlay = enableTextOverlay && textOverlay->visible;
|
bool submitOverlay = settings.overlay && UIOverlay->visible;
|
||||||
|
|
||||||
if (submitTextOverlay)
|
if (submitOverlay) {
|
||||||
{
|
|
||||||
// Wait for color attachment output to finish before rendering the text overlay
|
// Wait for color attachment output to finish before rendering the text overlay
|
||||||
VkPipelineStageFlags stageFlags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
VkPipelineStageFlags stageFlags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
submitInfo.pWaitDstStageMask = &stageFlags;
|
submitInfo.pWaitDstStageMask = &stageFlags;
|
||||||
|
|
@ -604,7 +603,7 @@ void VulkanExampleBase::submitFrame()
|
||||||
|
|
||||||
// Submit current text overlay command buffer
|
// Submit current text overlay command buffer
|
||||||
submitInfo.commandBufferCount = 1;
|
submitInfo.commandBufferCount = 1;
|
||||||
submitInfo.pCommandBuffers = &textOverlay->cmdBuffers[currentBuffer];
|
submitInfo.pCommandBuffers = &UIOverlay->cmdBuffers[currentBuffer];
|
||||||
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
|
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
|
||||||
|
|
||||||
// Reset stage mask
|
// Reset stage mask
|
||||||
|
|
@ -618,7 +617,7 @@ void VulkanExampleBase::submitFrame()
|
||||||
submitInfo.pSignalSemaphores = &semaphores.renderComplete;
|
submitInfo.pSignalSemaphores = &semaphores.renderComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
VK_CHECK_RESULT(swapChain.queuePresent(queue, currentBuffer, submitTextOverlay ? semaphores.textOverlayComplete : semaphores.renderComplete));
|
VK_CHECK_RESULT(swapChain.queuePresent(queue, currentBuffer, submitOverlay ? semaphores.textOverlayComplete : semaphores.renderComplete));
|
||||||
|
|
||||||
VK_CHECK_RESULT(vkQueueWaitIdle(queue));
|
VK_CHECK_RESULT(vkQueueWaitIdle(queue));
|
||||||
}
|
}
|
||||||
|
|
@ -732,9 +731,8 @@ VulkanExampleBase::~VulkanExampleBase()
|
||||||
vkDestroySemaphore(device, semaphores.renderComplete, nullptr);
|
vkDestroySemaphore(device, semaphores.renderComplete, nullptr);
|
||||||
vkDestroySemaphore(device, semaphores.textOverlayComplete, nullptr);
|
vkDestroySemaphore(device, semaphores.textOverlayComplete, nullptr);
|
||||||
|
|
||||||
if (enableTextOverlay)
|
if (UIOverlay) {
|
||||||
{
|
delete UIOverlay;
|
||||||
delete textOverlay;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete vulkanDevice;
|
delete vulkanDevice;
|
||||||
|
|
@ -1074,9 +1072,8 @@ void VulkanExampleBase::handleMessages(HWND hWnd, UINT uMsg, WPARAM wParam, LPAR
|
||||||
paused = !paused;
|
paused = !paused;
|
||||||
break;
|
break;
|
||||||
case KEY_F1:
|
case KEY_F1:
|
||||||
if (enableTextOverlay)
|
if (settings.overlay) {
|
||||||
{
|
UIOverlay->visible = !UIOverlay->visible;
|
||||||
textOverlay->visible = !textOverlay->visible;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KEY_ESCAPE:
|
case KEY_ESCAPE:
|
||||||
|
|
@ -2114,10 +2111,10 @@ void VulkanExampleBase::windowResize()
|
||||||
|
|
||||||
vkDeviceWaitIdle(device);
|
vkDeviceWaitIdle(device);
|
||||||
|
|
||||||
if (enableTextOverlay)
|
if (settings.overlay) {
|
||||||
{
|
// TODO: Check if still required
|
||||||
textOverlay->reallocateCommandBuffers();
|
UIOverlay->reallocateCommandBuffers();
|
||||||
updateTextOverlay();
|
updateOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
camera.updateAspectRatio((float)width / (float)height);
|
camera.updateAspectRatio((float)width / (float)height);
|
||||||
|
|
@ -2155,3 +2152,5 @@ void VulkanExampleBase::setupSwapChain()
|
||||||
{
|
{
|
||||||
swapChain.create(&width, &height, settings.vsync);
|
swapChain.create(&width, &height, settings.vsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VulkanExampleBase::OnUpdateUIOverlay() {}
|
||||||
|
|
@ -44,11 +44,11 @@
|
||||||
#include "keycodes.hpp"
|
#include "keycodes.hpp"
|
||||||
#include "VulkanTools.h"
|
#include "VulkanTools.h"
|
||||||
#include "VulkanDebug.h"
|
#include "VulkanDebug.h"
|
||||||
|
#include "VulkanUIOverlay.h"
|
||||||
|
|
||||||
#include "VulkanInitializers.hpp"
|
#include "VulkanInitializers.hpp"
|
||||||
#include "VulkanDevice.hpp"
|
#include "VulkanDevice.hpp"
|
||||||
#include "VulkanSwapChain.hpp"
|
#include "VulkanSwapChain.hpp"
|
||||||
#include "VulkanTextOverlay.hpp"
|
|
||||||
#include "camera.hpp"
|
#include "camera.hpp"
|
||||||
#include "benchmark.hpp"
|
#include "benchmark.hpp"
|
||||||
|
|
||||||
|
|
@ -149,6 +149,8 @@ public:
|
||||||
bool fullscreen = false;
|
bool fullscreen = false;
|
||||||
/** @brief Set to true if v-sync will be forced for the swapchain */
|
/** @brief Set to true if v-sync will be forced for the swapchain */
|
||||||
bool vsync = false;
|
bool vsync = false;
|
||||||
|
/** @brief Enable UI overlay */
|
||||||
|
bool overlay = false;
|
||||||
} settings;
|
} settings;
|
||||||
|
|
||||||
VkClearColorValue defaultClearColor = { { 0.025f, 0.025f, 0.025f, 1.0f } };
|
VkClearColorValue defaultClearColor = { { 0.025f, 0.025f, 0.025f, 1.0f } };
|
||||||
|
|
@ -165,8 +167,7 @@ public:
|
||||||
|
|
||||||
bool paused = false;
|
bool paused = false;
|
||||||
|
|
||||||
bool enableTextOverlay = false;
|
vks::UIOverlay *UIOverlay = nullptr;
|
||||||
VulkanTextOverlay *textOverlay;
|
|
||||||
|
|
||||||
// Use to adjust mouse rotation speed
|
// Use to adjust mouse rotation speed
|
||||||
float rotationSpeed = 1.0f;
|
float rotationSpeed = 1.0f;
|
||||||
|
|
@ -381,10 +382,7 @@ public:
|
||||||
// Render one frame of a render loop on platforms that sync rendering
|
// Render one frame of a render loop on platforms that sync rendering
|
||||||
void renderFrame();
|
void renderFrame();
|
||||||
|
|
||||||
void updateTextOverlay();
|
void updateOverlay();
|
||||||
|
|
||||||
/** @brief (Virtual) Called when the text overlay is updating, can be used to add custom text to the overlay */
|
|
||||||
virtual void getOverlayText(VulkanTextOverlay*);
|
|
||||||
|
|
||||||
// Prepare the frame for workload submission
|
// Prepare the frame for workload submission
|
||||||
// - Acquires the next image from the swap chain
|
// - Acquires the next image from the swap chain
|
||||||
|
|
@ -395,6 +393,8 @@ public:
|
||||||
// - Submits the text overlay (if enabled)
|
// - Submits the text overlay (if enabled)
|
||||||
void submitFrame();
|
void submitFrame();
|
||||||
|
|
||||||
|
/** @brief (Virtual) Called when the UI overlay is updating, can be used to add custom elements to the overlay */
|
||||||
|
virtual void OnUpdateUIOverlay();
|
||||||
};
|
};
|
||||||
|
|
||||||
// OS specific macros for the example main entry points
|
// OS specific macros for the example main entry points
|
||||||
|
|
|
||||||
13
data/shaders/base/uioverlay.frag
Normal file
13
data/shaders/base/uioverlay.frag
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (binding = 0) uniform sampler2D fontSampler;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 inUV;
|
||||||
|
layout (location = 1) in vec4 inColor;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
outColor = inColor * texture(fontSampler, inUV);
|
||||||
|
}
|
||||||
BIN
data/shaders/base/uioverlay.frag.spv
Normal file
BIN
data/shaders/base/uioverlay.frag.spv
Normal file
Binary file not shown.
25
data/shaders/base/uioverlay.vert
Normal file
25
data/shaders/base/uioverlay.vert
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 inPos;
|
||||||
|
layout (location = 1) in vec2 inUV;
|
||||||
|
layout (location = 2) in vec4 inColor;
|
||||||
|
|
||||||
|
layout (push_constant) uniform PushConstants {
|
||||||
|
vec2 scale;
|
||||||
|
vec2 translate;
|
||||||
|
} pushConstants;
|
||||||
|
|
||||||
|
layout (location = 0) out vec2 outUV;
|
||||||
|
layout (location = 1) out vec4 outColor;
|
||||||
|
|
||||||
|
out gl_PerVertex
|
||||||
|
{
|
||||||
|
vec4 gl_Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
outUV = inUV;
|
||||||
|
outColor = inColor;
|
||||||
|
gl_Position = vec4(inPos * pushConstants.scale + pushConstants.translate, 0.0, 1.0);
|
||||||
|
}
|
||||||
BIN
data/shaders/base/uioverlay.vert.spv
Normal file
BIN
data/shaders/base/uioverlay.vert.spv
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue