diff --git a/base/vulkantextoverlay.hpp b/base/vulkantextoverlay.hpp new file mode 100644 index 00000000..7e62a203 --- /dev/null +++ b/base/vulkantextoverlay.hpp @@ -0,0 +1,679 @@ +/* +* 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 +#include +#include +#include +#include +#include +#include + +#include +#include "vulkantools.h" + +#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 2048 + +// Mostly self-contained text overlay class +// todo : comment +class VulkanTextOverlay +{ +private: + VkPhysicalDevice physicalDevice; + VkDevice device; + VkPhysicalDeviceMemoryProperties deviceMemoryProperties; + VkQueue queue; + VkFormat colorFormat; + VkFormat depthFormat; + + uint32_t *frameBufferWidth; + uint32_t *frameBufferHeight; + + VkSampler sampler; + VkImage image; + VkImageView view; + VkBuffer buffer; + VkDeviceMemory memory; + VkDeviceMemory imageMemory; + VkDescriptorPool descriptorPool; + VkDescriptorSetLayout descriptorSetLayout; + VkDescriptorSet descriptorSet; + VkPipelineLayout pipelineLayout; + VkPipelineCache pipelineCache; + VkPipeline pipeline; + VkRenderPass renderPass; + VkCommandPool commandPool; + std::vector cmdBuffers; + std::vector frameBuffers; + std::vector shaderStages; + + // Pointer to mapped vertex buffer + glm::vec4 *mapped = nullptr; + + stb_fontchar stbFontData[STB_NUM_CHARS]; + uint32_t numLetters; + + // Try to find appropriate memory type for a memory allocation + VkBool32 getMemoryType(uint32_t typeBits, VkFlags properties, uint32_t *typeIndex) + { + for (int i = 0; i < 32; i++) { + if ((typeBits & 1) == 1) { + if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + *typeIndex = i; + return true; + } + } + typeBits >>= 1; + } + return false; + } +public: + + enum TextAlign { alignLeft, alignCenter, alignRight }; + + bool visible = true; + + VulkanTextOverlay( + VkPhysicalDevice physicalDevice, + VkDevice device, + VkQueue queue, + std::vector &framebuffers, + VkFormat colorformat, + VkFormat depthformat, + uint32_t *framebufferwidth, + uint32_t *framebufferheight, + std::vector shaderstages) + { + this->physicalDevice = physicalDevice; + this->device = device; + 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; + + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties); + cmdBuffers.resize(framebuffers.size()); + prepareResources(); + prepareRenderPass(); + preparePipeline(); + } + + ~VulkanTextOverlay() + { + // Free up all Vulkan resources requested by the text overlay + vkDestroySampler(device, sampler, nullptr); + vkDestroyImage(device, image, nullptr); + vkDestroyImageView(device, view, nullptr); + vkDestroyBuffer(device, buffer, nullptr); + vkFreeMemory(device, memory, nullptr); + vkFreeMemory(device, imageMemory, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyPipelineCache(device, pipelineCache, nullptr); + vkDestroyPipeline(device, pipeline, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + vkFreeCommandBuffers(device, commandPool, cmdBuffers.size(), cmdBuffers.data()); + vkDestroyCommandPool(device, commandPool, 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 = 0; // todo : pass from example base / swap chain + cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &commandPool)); + + VkCommandBufferAllocateInfo cmdBufAllocateInfo = + vkTools::initializers::commandBufferAllocateInfo( + commandPool, + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + (uint32_t)cmdBuffers.size()); + + VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, cmdBuffers.data())); + + // Vertex buffer + VkDeviceSize bufferSize = MAX_CHAR_COUNT * sizeof(glm::vec4); + + VkBufferCreateInfo bufferInfo = vkTools::initializers::bufferCreateInfo(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, bufferSize); + VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &buffer)); + + VkMemoryRequirements memReqs; + VkMemoryAllocateInfo allocInfo = vkTools::initializers::memoryAllocateInfo(); + + vkGetBufferMemoryRequirements(device, buffer, &memReqs); + allocInfo.allocationSize = memReqs.size; + getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &allocInfo.memoryTypeIndex); + + VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &memory)); + VK_CHECK_RESULT(vkBindBufferMemory(device, buffer, memory, 0)); + + // Font texture + VkImageCreateInfo imageInfo = vkTools::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(device, &imageInfo, nullptr, &image)); + + allocInfo.allocationSize = STB_FONT_WIDTH * STB_NUM_CHARS; + getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &allocInfo.memoryTypeIndex); + + VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory)); + VK_CHECK_RESULT(vkBindImageMemory(device, image, imageMemory, 0)); + + // Staging + + struct { + VkDeviceMemory memory; + VkBuffer buffer; + } stagingBuffer; + + VkBufferCreateInfo bufferCreateInfo = vkTools::initializers::bufferCreateInfo(); + bufferCreateInfo.size = allocInfo.allocationSize; + bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer.buffer)); + + // Get memory requirements for the staging buffer (alignment, memory type bits) + vkGetBufferMemoryRequirements(device, stagingBuffer.buffer, &memReqs); + + allocInfo.allocationSize = memReqs.size; + // Get memory type index for a host visible buffer + getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &allocInfo.memoryTypeIndex); + + VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &stagingBuffer.memory)); + VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer.buffer, stagingBuffer.memory, 0)); + + uint8_t *data; + VK_CHECK_RESULT(vkMapMemory(device, stagingBuffer.memory, 0, allocInfo.allocationSize, 0, (void **)&data)); + memcpy(data, &font24pixels[0][0], STB_FONT_WIDTH * STB_FONT_HEIGHT); + vkUnmapMemory(device, stagingBuffer.memory); + + // Copy to image + + VkCommandBuffer copyCmd; + cmdBufAllocateInfo.commandBufferCount = 1; + VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, ©Cmd)); + + VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); + VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); + + // Prepare for transfer + vkTools::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 + vkTools::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 = vkTools::initializers::submitInfo(); + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = ©Cmd; + + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + VK_CHECK_RESULT(vkQueueWaitIdle(queue)); + + vkFreeCommandBuffers(device, commandPool, 1, ©Cmd); + vkFreeMemory(device, stagingBuffer.memory, nullptr); + vkDestroyBuffer(device, stagingBuffer.buffer, nullptr); + + + VkImageViewCreateInfo imageViewInfo = vkTools::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(device, &imageViewInfo, nullptr, &view)); + + // Sampler + VkSamplerCreateInfo samplerInfo = vkTools::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; + VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &sampler)); + + // Descriptor + // Font uses a separate descriptor pool + std::array poolSizes; + poolSizes[0] = vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1); + + VkDescriptorPoolCreateInfo descriptorPoolInfo = + vkTools::initializers::descriptorPoolCreateInfo( + poolSizes.size(), + poolSizes.data(), + 1); + + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); + + // Descriptor set layout + std::array setLayoutBindings; + setLayoutBindings[0] = vkTools::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0); + + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo = + vkTools::initializers::descriptorSetLayoutCreateInfo( + setLayoutBindings.data(), + setLayoutBindings.size()); + + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutInfo, nullptr, &descriptorSetLayout)); + + // Pipeline layout + VkPipelineLayoutCreateInfo pipelineLayoutInfo = + vkTools::initializers::pipelineLayoutCreateInfo( + &descriptorSetLayout, + 1); + + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout)); + + // Descriptor set + VkDescriptorSetAllocateInfo descriptorSetAllocInfo = + vkTools::initializers::descriptorSetAllocateInfo( + descriptorPool, + &descriptorSetLayout, + 1); + + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSet)); + + VkDescriptorImageInfo texDescriptor = + vkTools::initializers::descriptorImageInfo( + sampler, + view, + VK_IMAGE_LAYOUT_GENERAL); + + std::array writeDescriptorSets; + writeDescriptorSets[0] = vkTools::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &texDescriptor); + vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + + // Pipeline cache + VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {}; + pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache)); + } + + // Prepare a separate pipeline for the font rendering decoupled from the main application + void preparePipeline() + { + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = + vkTools::initializers::pipelineInputAssemblyStateCreateInfo( + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, + 0, + VK_FALSE); + + VkPipelineRasterizationStateCreateInfo rasterizationState = + vkTools::initializers::pipelineRasterizationStateCreateInfo( + VK_POLYGON_MODE_FILL, + VK_CULL_MODE_BACK_BIT, + VK_FRONT_FACE_CLOCKWISE, + 0); + + // Enable blending + VkPipelineColorBlendAttachmentState blendAttachmentState = + vkTools::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 = + vkTools::initializers::pipelineColorBlendStateCreateInfo( + 1, + &blendAttachmentState); + + VkPipelineDepthStencilStateCreateInfo depthStencilState = + vkTools::initializers::pipelineDepthStencilStateCreateInfo( + VK_TRUE, + VK_TRUE, + VK_COMPARE_OP_LESS_OR_EQUAL); + + VkPipelineViewportStateCreateInfo viewportState = + vkTools::initializers::pipelineViewportStateCreateInfo(1, 1, 0); + + VkPipelineMultisampleStateCreateInfo multisampleState = + vkTools::initializers::pipelineMultisampleStateCreateInfo( + VK_SAMPLE_COUNT_1_BIT, + 0); + + std::vector dynamicStateEnables = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + + VkPipelineDynamicStateCreateInfo dynamicState = + vkTools::initializers::pipelineDynamicStateCreateInfo( + dynamicStateEnables.data(), + dynamicStateEnables.size(), + 0); + + std::array vertexBindings = {}; + vertexBindings[0] = vkTools::initializers::vertexInputBindingDescription(0, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX); + vertexBindings[1] = vkTools::initializers::vertexInputBindingDescription(1, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX); + + std::array vertexAttribs = {}; + // Position + vertexAttribs[0] = vkTools::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, 0); + // UV + vertexAttribs[1] = vkTools::initializers::vertexInputAttributeDescription(1, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(glm::vec2)); + + VkPipelineVertexInputStateCreateInfo inputState = vkTools::initializers::pipelineVertexInputStateCreateInfo(); + inputState.vertexBindingDescriptionCount = vertexBindings.size(); + inputState.pVertexBindingDescriptions = vertexBindings.data(); + inputState.vertexAttributeDescriptionCount = vertexAttribs.size(); + inputState.pVertexAttributeDescriptions = vertexAttribs.data(); + + VkGraphicsPipelineCreateInfo pipelineCreateInfo = + vkTools::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 = shaderStages.size(); + pipelineCreateInfo.pStages = shaderStages.data(); + + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, 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_COLOR_ATTACHMENT_OPTIMAL; + attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + // Depth attachment + attachments[1].format = depthFormat; + attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + 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; + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.flags = 0; + subpass.inputAttachmentCount = 0; + subpass.pInputAttachments = NULL; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorReference; + subpass.pResolveAttachments = NULL; + subpass.pDepthStencilAttachment = &depthReference; + subpass.preserveAttachmentCount = 0; + subpass.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 = &subpass; + renderPassInfo.dependencyCount = 0; + renderPassInfo.pDependencies = NULL; + + VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass)); + } + + // Map buffer + void beginTextUpdate() + { + VK_CHECK_RESULT(vkMapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, (void **)&mapped)); + numLetters = 0; + } + + // Add text to the current buffer + // todo : drop shadow? color attribute? + void addText(std::string text, float x, float y, TextAlign align) + { + assert(mapped != nullptr); + + const float charW = 1.5f / *frameBufferWidth; + const float charH = 1.5f / *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]; + + mapped->x = (x + (float)charData->x0 * charW); + mapped->y = (y + (float)charData->y0 * charH); + mapped->z = charData->s0; + mapped->w = charData->t0; + mapped++; + + mapped->x = (x + (float)charData->x1 * charW); + mapped->y = (y + (float)charData->y0 * charH); + mapped->z = charData->s1; + mapped->w = charData->t0; + mapped++; + + mapped->x = (x + (float)charData->x0 * charW); + mapped->y = (y + (float)charData->y1 * charH); + mapped->z = charData->s0; + mapped->w = charData->t1; + mapped++; + + mapped->x = (x + (float)charData->x1 * charW); + mapped->y = (y + (float)charData->y1 * charH); + mapped->z = charData->s1; + mapped->w = charData->t1; + mapped++; + + x += charData->advance * charW; + + numLetters++; + } + } + + // Unmap buffer and update command buffers + void endTextUpdate() + { + vkUnmapMemory(device, memory); + mapped = nullptr; + updateCommandBuffers(); + } + + // Needs to be called by the application + void updateCommandBuffers() + { + VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); + + VkClearValue clearValues[1]; + clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } }; + + VkRenderPassBeginInfo renderPassBeginInfo = vkTools::initializers::renderPassBeginInfo(); + renderPassBeginInfo.renderPass = renderPass; + renderPassBeginInfo.renderArea.extent.width = *frameBufferWidth; + renderPassBeginInfo.renderArea.extent.height = *frameBufferHeight; + renderPassBeginInfo.clearValueCount = 1; + renderPassBeginInfo.pClearValues = clearValues; + + for (int32_t i = 0; i < cmdBuffers.size(); ++i) + { + renderPassBeginInfo.framebuffer = *frameBuffers[i]; + + VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffers[i], &cmdBufInfo)); + + vkCmdBeginRenderPass(cmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + + VkViewport viewport = vkTools::initializers::viewport((float)*frameBufferWidth, (float)*frameBufferHeight, 0.0f, 1.0f); + vkCmdSetViewport(cmdBuffers[i], 0, 1, &viewport); + + VkRect2D scissor = vkTools::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, &buffer, &offsets); + vkCmdBindVertexBuffers(cmdBuffers[i], 1, 1, &buffer, &offsets); + for (uint32_t j = 0; j < numLetters; j++) + { + vkCmdDraw(cmdBuffers[i], 4, 1, j * 4, 0); + } + + + vkCmdEndRenderPass(cmdBuffers[i]); + + VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuffers[i])); + } + } + + // Submit the text command buffers to a queue + // Does a queue wait idle + void submit(VkQueue queue, uint32_t bufferindex) + { + if (!visible) + { + return; + } + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &cmdBuffers[bufferindex]; + + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + VK_CHECK_RESULT(vkQueueWaitIdle(queue)); + } + +}; \ No newline at end of file diff --git a/data/shaders/base/generate-spirv.bat b/data/shaders/base/generate-spirv.bat new file mode 100644 index 00000000..a4ede2cd --- /dev/null +++ b/data/shaders/base/generate-spirv.bat @@ -0,0 +1,2 @@ +glslangvalidator -V textoverlay.vert -o textoverlay.vert.spv +glslangvalidator -V textoverlay.frag -o textoverlay.frag.spv \ No newline at end of file diff --git a/data/shaders/base/textoverlay.frag b/data/shaders/base/textoverlay.frag new file mode 100644 index 00000000..83ceb1ea --- /dev/null +++ b/data/shaders/base/textoverlay.frag @@ -0,0 +1,13 @@ +#version 450 core + +layout (location = 0) in vec2 inUV; + +layout (binding = 0) uniform sampler2D samplerFont; + +layout (location = 0) out vec4 outFragColor; + +void main(void) +{ + float color = texture(samplerFont, inUV).r; + outFragColor = vec4(vec3(color), 1.0); +} diff --git a/data/shaders/base/textoverlay.frag.spv b/data/shaders/base/textoverlay.frag.spv new file mode 100644 index 00000000..4aea3bdf Binary files /dev/null and b/data/shaders/base/textoverlay.frag.spv differ diff --git a/data/shaders/base/textoverlay.vert b/data/shaders/base/textoverlay.vert new file mode 100644 index 00000000..548bfdb2 --- /dev/null +++ b/data/shaders/base/textoverlay.vert @@ -0,0 +1,17 @@ +#version 450 core + +layout (location = 0) in vec2 inPos; +layout (location = 1) in vec2 inUV; + +layout (location = 0) out vec2 outUV; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main(void) +{ + gl_Position = vec4(inPos, 0.0, 1.0); + outUV = inUV; +} diff --git a/data/shaders/base/textoverlay.vert.spv b/data/shaders/base/textoverlay.vert.spv new file mode 100644 index 00000000..fd292c04 Binary files /dev/null and b/data/shaders/base/textoverlay.vert.spv differ