10 VkCommandPool cmdPool,
const std::string &fontPath,
int fontSize)
13 if (TTF_Init() == -1) {
14 throw mx::Exception(
"Failed to initialize SDL_ttf: " + std::string(TTF_GetError()));
41 if (cached.imageView != VK_NULL_HANDLE) {
42 vkDestroyImageView(
device, cached.imageView,
nullptr);
44 if (cached.image != VK_NULL_HANDLE) {
45 vkDestroyImage(
device, cached.image,
nullptr);
46 vkFreeMemory(
device, cached.imageMemory,
nullptr);
57 VkDescriptorPoolSize poolSize{};
58 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
59 poolSize.descriptorCount = maxSets;
61 VkDescriptorPoolCreateInfo poolInfo{};
62 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
63 poolInfo.poolSizeCount = 1;
64 poolInfo.pPoolSizes = &poolSize;
65 poolInfo.maxSets = maxSets;
66 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
85 throw mx::Exception(
"VKText::createDescriptorSet called before setDescriptorSetLayout");
92 VkDescriptorSetAllocateInfo allocInfo{};
93 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
95 allocInfo.descriptorSetCount = 1;
98 VkDescriptorSet descriptorSet;
99 VkResult res = vkAllocateDescriptorSets(
device, &allocInfo, &descriptorSet);
100 if (res == VK_ERROR_OUT_OF_POOL_MEMORY || res == VK_ERROR_FRAGMENTED_POOL) {
104 }
else if (res != VK_SUCCESS) {
105 throw mx::Exception(
"Fatal : VkResult is \"" + std::to_string(res) +
"\" in " + std::string(__FILE__) +
" at line " + std::to_string(__LINE__));
108 VkDescriptorImageInfo imageInfo{};
109 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
110 imageInfo.imageView = imageView;
113 VkWriteDescriptorSet descriptorWrite{};
114 descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
115 descriptorWrite.dstSet = descriptorSet;
116 descriptorWrite.dstBinding = 0;
117 descriptorWrite.dstArrayElement = 0;
118 descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
119 descriptorWrite.descriptorCount = 1;
120 descriptorWrite.pImageInfo = &imageInfo;
122 vkUpdateDescriptorSets(
device, 1, &descriptorWrite, 0,
nullptr);
124 return descriptorSet;
128 font = TTF_OpenFont(fontPath.c_str(), fontSize);
130 throw mx::Exception(
"Failed to load font: " + std::string(TTF_GetError()));
133 VkSamplerCreateInfo samplerInfo{};
134 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
135 samplerInfo.magFilter = VK_FILTER_LINEAR;
136 samplerInfo.minFilter = VK_FILTER_LINEAR;
137 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
138 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
139 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
140 samplerInfo.anisotropyEnable = VK_FALSE;
141 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
142 samplerInfo.unnormalizedCoordinates = VK_FALSE;
143 samplerInfo.compareEnable = VK_FALSE;
144 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
145 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
166 SDL_Surface* converted = SDL_CreateRGBSurface(0, surface->w, surface->h, 32,
167 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
168 if (!converted)
return nullptr;
169 SDL_BlitSurface(surface,
nullptr, converted,
nullptr);
174 VkImageViewCreateInfo viewInfo{};
175 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
176 viewInfo.image = image;
177 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
178 viewInfo.format = format;
179 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
180 viewInfo.subresourceRange.baseMipLevel = 0;
181 viewInfo.subresourceRange.levelCount = 1;
182 viewInfo.subresourceRange.baseArrayLayer = 0;
183 viewInfo.subresourceRange.layerCount = 1;
185 VkImageView imageView;
191 if (text.empty() || !
font)
return;
201 CacheKey key{text, col.r, col.g, col.b, col.a};
206 auto &cached = it->second;
210 quad.
width = cached.width;
211 quad.
height = cached.height;
214 SDL_Surface *textSurface = TTF_RenderText_Blended(
font, text.c_str(), col);
215 if (!textSurface)
return;
218 SDL_FreeSurface(textSurface);
219 if (!rgbaSurface)
return;
221 quad.
width = rgbaSurface->w;
222 quad.
height = rgbaSurface->h;
224 createImage(rgbaSurface->w, rgbaSurface->h, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
225 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
228 VkBuffer stagingBuffer;
229 VkDeviceMemory stagingMemory;
230 VkDeviceSize imageSize = rgbaSurface->w * rgbaSurface->h * 4;
232 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
233 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
234 stagingBuffer, stagingMemory);
238 memcpy(data, rgbaSurface->pixels, imageSize);
239 vkUnmapMemory(
device, stagingMemory);
245 vkDestroyBuffer(
device, stagingBuffer,
nullptr);
246 vkFreeMemory(
device, stagingMemory,
nullptr);
250 SDL_FreeSurface(rgbaSurface);
260 float x1 = x0 + quad.
width;
261 float y1 = y0 + quad.
height;
269 quad.
indices = {0, 1, 2, 0, 2, 3};
274 createBuffer(vertexSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
275 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
279 memcpy(data, quad.
vertices.data(), vertexSize);
282 VkDeviceSize indexSize = quad.
indices.size() *
sizeof(uint16_t);
283 createBuffer(indexSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
284 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
288 memcpy(data, quad.
indices.data(), indexSize);
297 uint32_t screenWidth, uint32_t screenHeight) {
299 struct TextPushConstants {
302 } pc {
static_cast<float>(screenWidth),
static_cast<float>(screenHeight) };
304 vkCmdPushConstants(cmdBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(TextPushConstants), &pc);
307 VkBuffer vertexBuffers[] = {quad.vertexBuffer};
308 VkDeviceSize offsets[] = {0};
309 vkCmdBindVertexBuffers(cmdBuffer, 0, 1, vertexBuffers, offsets);
310 vkCmdBindIndexBuffer(cmdBuffer, quad.indexBuffer, 0, VK_INDEX_TYPE_UINT16);
311 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &quad.descriptorSet, 0,
nullptr);
313 vkCmdDrawIndexed(cmdBuffer, quad.indexCount, 1, 0, 0, 0);
325 VkMemoryPropertyFlags properties, VkBuffer& buffer,
326 VkDeviceMemory& bufferMemory) {
327 VkBufferCreateInfo bufferInfo{};
328 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
329 bufferInfo.size = size;
330 bufferInfo.usage = usage;
331 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
335 VkMemoryRequirements memRequirements;
336 vkGetBufferMemoryRequirements(
device, buffer, &memRequirements);
338 VkMemoryAllocateInfo allocInfo{};
339 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
340 allocInfo.allocationSize = memRequirements.size;
341 allocInfo.memoryTypeIndex =
findMemoryType(memRequirements.memoryTypeBits, properties);
348 if (text.empty() || !
font) {
354 if (TTF_SizeText(
font, text.c_str(), &width, &height) == 0) {
361 VkPhysicalDeviceMemoryProperties memProperties;
362 vkGetPhysicalDeviceMemoryProperties(
physicalDevice, &memProperties);
364 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
365 if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
376 VkImageMemoryBarrier barrier{};
377 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
378 barrier.oldLayout = oldLayout;
379 barrier.newLayout = newLayout;
380 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
381 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
382 barrier.image = image;
383 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
384 barrier.subresourceRange.baseMipLevel = 0;
385 barrier.subresourceRange.levelCount = 1;
386 barrier.subresourceRange.baseArrayLayer = 0;
387 barrier.subresourceRange.layerCount = 1;
389 VkPipelineStageFlags sourceStage;
390 VkPipelineStageFlags destinationStage;
392 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
393 barrier.srcAccessMask = 0;
394 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
395 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
396 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
397 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
398 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
399 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
400 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
401 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
403 throw std::invalid_argument(
"unsupported layout transition!");
406 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
413 VkBufferImageCopy region{};
414 region.bufferOffset = 0;
415 region.bufferRowLength = 0;
416 region.bufferImageHeight = 0;
417 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
418 region.imageSubresource.mipLevel = 0;
419 region.imageSubresource.baseArrayLayer = 0;
420 region.imageSubresource.layerCount = 1;
421 region.imageOffset = {0, 0, 0};
422 region.imageExtent = {width, height, 1};
424 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
430 VkCommandBufferAllocateInfo allocInfo{};
431 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
432 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
434 allocInfo.commandBufferCount = 1;
436 VkCommandBuffer commandBuffer;
439 VkCommandBufferBeginInfo beginInfo{};
440 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
441 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
445 return commandBuffer;
451 VkSubmitInfo submitInfo{};
452 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
453 submitInfo.commandBufferCount = 1;
454 submitInfo.pCommandBuffers = &commandBuffer;
463 VkImageUsageFlags usage, VkMemoryPropertyFlags properties,
464 VkImage& image, VkDeviceMemory& imageMemory) {
465 VkImageCreateInfo imageInfo{};
466 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
467 imageInfo.imageType = VK_IMAGE_TYPE_2D;
468 imageInfo.extent.width = width;
469 imageInfo.extent.height = height;
470 imageInfo.extent.depth = 1;
471 imageInfo.mipLevels = 1;
472 imageInfo.arrayLayers = 1;
473 imageInfo.format = format;
474 imageInfo.tiling = tiling;
475 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
476 imageInfo.usage = usage;
477 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
478 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
482 VkMemoryRequirements memRequirements;
483 vkGetImageMemoryRequirements(
device, image, &memRequirements);
485 VkMemoryAllocateInfo allocInfo{};
486 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
487 allocInfo.allocationSize = memRequirements.size;
488 allocInfo.memoryTypeIndex =
findMemoryType(memRequirements.memoryTypeBits, properties);
General-purpose exception class for the mx library.
VkPhysicalDevice physicalDevice
void printTextG_Solid(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for solid (opaque) rendering.
VkDescriptorSetLayout descriptorSetLayout
VkDescriptorSet createDescriptorSet(VkImageView imageView)
void initFont(const std::string &fontPath, int fontSize)
void growDescriptorPool()
VkCommandBuffer beginSingleTimeCommands()
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
VkDescriptorPool descriptorPool
VkSampler fontSampler
Sampler used for all text textures.
void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
void renderText(VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout, uint32_t screenWidth, uint32_t screenHeight)
Record all queued text quads into a command buffer.
void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &imageMemory)
~VKText()
Destructor – destroys all Vulkan and SDL_ttf resources.
void endSingleTimeCommands(VkCommandBuffer commandBuffer)
std::unordered_map< CacheKey, CachedTexture, CacheKeyHash > textureCache
SDL_Surface * convertToRGBA(SDL_Surface *surface)
void createDescriptorPool()
void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
void setFont(const std::string &fontPath, int fontSize)
Change the active font.
void clearCache()
Destroy all cached text textures, freeing GPU memory.
VkImageView createImageView(VkImage image, VkFormat format)
VkCommandPool commandPool
void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
bool getTextDimensions(const std::string &text, int &width, int &height)
Measure the pixel dimensions of a string with the current font.
VKText(VkDevice device, VkPhysicalDevice physicalDevice, VkQueue graphicsQueue, VkCommandPool commandPool, const std::string &fontPath, int fontSize=24)
Construct VKText and load the font.
void clearQueue()
Discard all pending text quads without rendering them.
std::vector< TextQuad > textQuads
Cache key combining text content and colour.
bool ownsTexture
false when texture is owned by the cache.
VkDescriptorSet descriptorSet
VkDeviceMemory vertexBufferMemory
VkDeviceMemory indexBufferMemory
VkImageView textImageView
VkDeviceMemory textImageMemory
std::vector< TextVertex > vertices
std::vector< uint16_t > indices
#define VK_CHECK_RESULT(f)
Vulkan SDL_ttf text renderer.