11 reset(fontPath, fontSize);
19 : font(std::exchange(other.font,
nullptr)),
20 font_path(std::move(other.font_path)),
21 font_size(std::exchange(other.font_size, 0)),
22 ownsTtfInit(std::exchange(other.ownsTtfInit,
false)) {}
27 font = std::exchange(other.font,
nullptr);
28 font_path = std::move(other.font_path);
29 font_size = std::exchange(other.font_size, 0);
30 ownsTtfInit = std::exchange(other.ownsTtfInit,
false);
36 if (font !=
nullptr) {
49 if (fontPath.empty() || fontSize <= 0) {
50 throw mxvk::Exception(
"Font requires a non-empty path and positive font size");
56 throw mxvk::Exception(
"Failed to initialize SDL_ttf: " + std::string(SDL_GetError()));
59 TTF_Font *newFont = TTF_OpenFont(fontPath.c_str(), fontSize);
60 if (newFont ==
nullptr) {
62 throw mxvk::Exception(
"Failed to load font: " + std::string(SDL_GetError()));
72 VkCommandPool cmdPool,
const std::string &fontPath,
int fontSize)
73 : device(dev), physicalDevice(physDev), graphicsQueue(gQueue), commandPool(cmdPool) {
76 throw mxvk::Exception(
"Failed to initialize SDL_ttf: " + std::string(SDL_GetError()));
79 initFont(fontPath, fontSize);
83 vkDeviceWaitIdle(device);
87 if (descriptorPool != VK_NULL_HANDLE) {
88 std::cout <<
"vk: destroying text descriptor pool\n";
89 vkDestroyDescriptorPool(device, descriptorPool,
nullptr);
93 std::cout <<
"vk: destroying text font sampler\n";
98 std::cout <<
"mxvk: closing text font\n";
105 for (
auto &[key, cached] : textureCache) {
106 destroyCachedTexture(cached);
108 textureCache.clear();
112 if (cached.
imageView != VK_NULL_HANDLE) {
113 vkDestroyImageView(device, cached.
imageView,
nullptr);
116 if (cached.
image != VK_NULL_HANDLE) {
117 vkDestroyImage(device, cached.
image,
nullptr);
118 cached.
image = VK_NULL_HANDLE;
126 void VK_Text::pruneCache() {
127 if (textureCache.size() <= MAX_CACHED_TEXTURES) {
131 std::vector<std::pair<uint64_t, CacheKey>> entries;
132 entries.reserve(textureCache.size());
133 for (
const auto &[key, cached] : textureCache) {
137 const size_t removeCount = textureCache.size() - MAX_CACHED_TEXTURES;
140 entries.begin() +
static_cast<std::ptrdiff_t
>(removeCount),
142 [](
const auto &lhs,
const auto &rhs) {
143 return lhs.first < rhs.first;
146 for (
size_t i = 0; i < removeCount; ++i) {
147 auto it = textureCache.find(entries[i].second);
148 if (it == textureCache.end()) {
151 destroyCachedTexture(it->second);
152 textureCache.erase(it);
156 void VK_Text::createDescriptorPool() {
157 createDescriptorPool(maxPoolSets);
160 void VK_Text::createDescriptorPool(uint32_t maxSets) {
161 VkDescriptorPoolSize poolSize{};
162 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
163 poolSize.descriptorCount = maxSets;
165 VkDescriptorPoolCreateInfo poolInfo{};
166 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
167 poolInfo.poolSizeCount = 1;
168 poolInfo.pPoolSizes = &poolSize;
169 poolInfo.maxSets = maxSets;
170 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
172 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo,
nullptr, &descriptorPool));
173 maxPoolSets = maxSets;
176 void VK_Text::growDescriptorPool() {
177 vkDeviceWaitIdle(device);
179 if (descriptorPool != VK_NULL_HANDLE) {
180 vkDestroyDescriptorPool(device, descriptorPool,
nullptr);
181 descriptorPool = VK_NULL_HANDLE;
184 createDescriptorPool(maxPoolSets);
187 VkDescriptorSet VK_Text::createDescriptorSet(VkImageView imageView) {
188 if (descriptorSetLayout == VK_NULL_HANDLE) {
189 throw mxvk::Exception(
"VKText::createDescriptorSet called before setDescriptorSetLayout");
192 if (descriptorPool == VK_NULL_HANDLE) {
193 createDescriptorPool();
196 VkDescriptorSetAllocateInfo allocInfo{};
197 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
198 allocInfo.descriptorPool = descriptorPool;
199 allocInfo.descriptorSetCount = 1;
200 allocInfo.pSetLayouts = &descriptorSetLayout;
202 VkDescriptorSet descriptorSet;
203 VkResult res = vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet);
204 if (res == VK_ERROR_OUT_OF_POOL_MEMORY || res == VK_ERROR_FRAGMENTED_POOL) {
205 growDescriptorPool();
206 allocInfo.descriptorPool = descriptorPool;
207 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
208 }
else if (res != VK_SUCCESS) {
209 throw mxvk::Exception(std::format(
"Fatal : VkResult is \"{}\" in {} at line {}",
static_cast<int>(res), __FILE__, __LINE__));
212 VkDescriptorImageInfo imageInfo{};
213 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
214 imageInfo.imageView = imageView;
217 VkWriteDescriptorSet descriptorWrite{};
218 descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
219 descriptorWrite.dstSet = descriptorSet;
220 descriptorWrite.dstBinding = 0;
221 descriptorWrite.dstArrayElement = 0;
222 descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
223 descriptorWrite.descriptorCount = 1;
224 descriptorWrite.pImageInfo = &imageInfo;
226 vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0,
nullptr);
228 return descriptorSet;
231 void VK_Text::initSampler() {
232 VkSamplerCreateInfo samplerInfo{};
233 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
234 samplerInfo.magFilter = VK_FILTER_LINEAR;
235 samplerInfo.minFilter = VK_FILTER_LINEAR;
236 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
237 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
238 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
239 samplerInfo.anisotropyEnable = VK_FALSE;
240 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
241 samplerInfo.unnormalizedCoordinates = VK_FALSE;
242 samplerInfo.compareEnable = VK_FALSE;
243 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
244 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
249 void VK_Text::initFont(
const std::string &fontPath,
int fontSize) {
250 font = TTF_OpenFont(fontPath.c_str(), fontSize);
252 throw mxvk::Exception(
"Failed to load font: " + std::string(SDL_GetError()));
257 std::cout << std::format(
"mxvk: Font loaded: {} @ {}pt\n", fontPath, fontSize);
261 vkDeviceWaitIdle(device);
272 initFont(fontPath, fontSize);
275 SDL_Surface *VK_Text::convertToRGBA(SDL_Surface *
surface) {
277 SDL_Surface *converted = SDL_ConvertSurface(
surface, SDL_PIXELFORMAT_RGBA32);
281 VkImageView VK_Text::createImageView(VkImage image, VkFormat format) {
282 VkImageViewCreateInfo viewInfo{};
283 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
284 viewInfo.image = image;
285 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
286 viewInfo.format = format;
287 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
288 viewInfo.subresourceRange.baseMipLevel = 0;
289 viewInfo.subresourceRange.levelCount = 1;
290 viewInfo.subresourceRange.baseArrayLayer = 0;
291 viewInfo.subresourceRange.layerCount = 1;
293 VkImageView imageView;
294 VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo,
nullptr, &imageView));
299 printTextG_SolidWithFont(text, x, y, col, font);
303 printTextG_SolidWithFont(text, x, y, col, textFont);
307 printTextG_SolidWithFont(text, x, y, col, textFont.
get());
310 void VK_Text::printTextG_SolidWithFont(
const std::string &text,
int x,
int y,
const SDL_Color &col, TTF_Font *textFont) {
311 if (text.empty() || !textFont)
319 quad.device = device;
324 quad.alpha =
static_cast<float>(col.a) / 255.0f;
325 quad.ownsTexture =
false;
327 SDL_Color textureColor = col;
328 textureColor.a = 255;
329 CacheKey key{text, textFont, textureColor.r, textureColor.g, textureColor.b, textureColor.a};
330 auto it = textureCache.find(key);
332 if (it != textureCache.end()) {
334 auto &cached = it->second;
336 quad.textImage = cached.
image;
339 quad.width = cached.
width;
340 quad.height = cached.
height;
344 SDL_Surface *textSurface = TTF_RenderText_Blended(textFont, text.c_str(), 0, textureColor);
349 SDL_Surface *rgbaSurface = convertToRGBA(textSurface);
350 SDL_DestroySurface(textSurface);
355 quad.width = rgbaSurface->w;
356 quad.height = rgbaSurface->h;
358 VkImage uploadedImage = VK_NULL_HANDLE;
359 VkDeviceMemory uploadedImageMemory = VK_NULL_HANDLE;
360 VkImageView uploadedImageView = VK_NULL_HANDLE;
361 VkBuffer stagingBuffer = VK_NULL_HANDLE;
362 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
363 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(rgbaSurface->w) *
static_cast<VkDeviceSize
>(rgbaSurface->h) * 4u;
365 auto cleanupUploadResources = [&]() {
366 if (stagingBuffer != VK_NULL_HANDLE) {
367 vkDestroyBuffer(device, stagingBuffer,
nullptr);
368 stagingBuffer = VK_NULL_HANDLE;
370 if (stagingMemory != VK_NULL_HANDLE) {
371 vkFreeMemory(device, stagingMemory,
nullptr);
372 stagingMemory = VK_NULL_HANDLE;
374 if (uploadedImageView != VK_NULL_HANDLE) {
375 vkDestroyImageView(device, uploadedImageView,
nullptr);
376 uploadedImageView = VK_NULL_HANDLE;
378 if (uploadedImage != VK_NULL_HANDLE) {
379 vkDestroyImage(device, uploadedImage,
nullptr);
380 uploadedImage = VK_NULL_HANDLE;
382 if (uploadedImageMemory != VK_NULL_HANDLE) {
383 vkFreeMemory(device, uploadedImageMemory,
nullptr);
384 uploadedImageMemory = VK_NULL_HANDLE;
388 bool uploadSucceeded =
false;
390 createImage(rgbaSurface->w, rgbaSurface->h, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
391 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
392 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, uploadedImage, uploadedImageMemory);
394 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
395 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
396 stagingBuffer, stagingMemory);
398 void *data =
nullptr;
399 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
400 memcpy(data, rgbaSurface->pixels,
static_cast<size_t>(imageSize));
401 vkUnmapMemory(device, stagingMemory);
403 if (!transitionImageLayout(uploadedImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)) {
404 SDL_DestroySurface(rgbaSurface);
405 cleanupUploadResources();
408 if (!copyBufferToImage(stagingBuffer, uploadedImage,
static_cast<uint32_t
>(rgbaSurface->w),
static_cast<uint32_t
>(rgbaSurface->h))) {
409 SDL_DestroySurface(rgbaSurface);
410 cleanupUploadResources();
413 if (!transitionImageLayout(uploadedImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)) {
414 SDL_DestroySurface(rgbaSurface);
415 cleanupUploadResources();
419 uploadedImageView = createImageView(uploadedImage, VK_FORMAT_R8G8B8A8_UNORM);
420 uploadSucceeded =
true;
421 }
catch (
const mxvk::Exception &ex) {
422 SDL_DestroySurface(rgbaSurface);
423 cleanupUploadResources();
424 static bool textTextureWarningLogged =
false;
425 if (!textTextureWarningLogged) {
426 std::cerr <<
"mxvk: dropping text texture after Vulkan allocation failure: " << ex.
text() <<
"\n";
427 textTextureWarningLogged =
true;
432 SDL_DestroySurface(rgbaSurface);
434 if (!uploadSucceeded) {
435 cleanupUploadResources();
439 if (stagingBuffer != VK_NULL_HANDLE) {
440 vkDestroyBuffer(device, stagingBuffer,
nullptr);
441 stagingBuffer = VK_NULL_HANDLE;
443 if (stagingMemory != VK_NULL_HANDLE) {
444 vkFreeMemory(device, stagingMemory,
nullptr);
445 stagingMemory = VK_NULL_HANDLE;
448 quad.textImage = uploadedImage;
449 quad.textImageMemory = uploadedImageMemory;
450 quad.textImageView = uploadedImageView;
453 textureCache[key] = {quad.textImage, quad.textImageMemory, quad.textImageView,
454 quad.width, quad.height, ++cacheUseSerial};
460 float x1 = x0 + quad.width;
461 float y1 = y0 + quad.height;
463 TextVertex v0 = {{x0, y0}, {0.0f, 0.0f}};
464 TextVertex v1 = {{x1, y0}, {1.0f, 0.0f}};
465 TextVertex v2 = {{x1, y1}, {1.0f, 1.0f}};
466 TextVertex v3 = {{x0, y1}, {0.0f, 1.0f}};
468 quad.vertices = {v0, v1, v2, v3};
469 quad.indices = {0, 1, 2, 0, 2, 3};
474 VkDeviceSize vertexSize = quad.vertices.size() *
sizeof(TextVertex);
475 createBuffer(vertexSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
476 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
477 quad.vertexBuffer, quad.vertexBufferMemory);
479 VK_CHECK_RESULT(vkMapMemory(device, quad.vertexBufferMemory, 0, vertexSize, 0, &data));
480 memcpy(data, quad.vertices.data(), vertexSize);
481 vkUnmapMemory(device, quad.vertexBufferMemory);
483 VkDeviceSize indexSize = quad.indices.size() *
sizeof(uint16_t);
484 createBuffer(indexSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
485 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
486 quad.indexBuffer, quad.indexBufferMemory);
488 VK_CHECK_RESULT(vkMapMemory(device, quad.indexBufferMemory, 0, indexSize, 0, &data));
489 memcpy(data, quad.indices.data(), indexSize);
490 vkUnmapMemory(device, quad.indexBufferMemory);
492 quad.descriptorSet = createDescriptorSet(quad.textImageView);
493 }
catch (
const mxvk::Exception &ex) {
494 static bool textAllocationWarningLogged =
false;
495 if (!textAllocationWarningLogged) {
496 std::cerr <<
"mxvk: dropping text draw after Vulkan allocation failure: " << ex.
text() <<
"\n";
497 textAllocationWarningLogged =
true;
502 textQuads.emplace_back(std::move(quad));
506 uint32_t screenWidth, uint32_t screenHeight) {
508 struct TextPushConstants {
513 } pc{
static_cast<float>(screenWidth),
static_cast<float>(screenHeight), 1.0f, 0.0f};
515 for (
auto &quad : textQuads) {
516 pc.alpha = quad.alpha;
517 vkCmdPushConstants(cmdBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(TextPushConstants), &pc);
518 VkBuffer vertexBuffers[] = {quad.vertexBuffer};
519 VkDeviceSize offsets[] = {0};
520 vkCmdBindVertexBuffers(cmdBuffer, 0, 1, vertexBuffers, offsets);
521 vkCmdBindIndexBuffer(cmdBuffer, quad.indexBuffer, 0, VK_INDEX_TYPE_UINT16);
522 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &quad.descriptorSet, 0,
nullptr);
524 vkCmdDrawIndexed(cmdBuffer, quad.indexCount, 1, 0, 0, 0);
529 if (textQuads.empty()) {
532 if (device == VK_NULL_HANDLE) {
539 const VkResult queue_idle_result = vkQueueWaitIdle(graphicsQueue);
540 if (queue_idle_result == VK_ERROR_DEVICE_LOST) {
541 std::cerr <<
"mxvk: Device lost while clearing queue; skipping text resource reset\n";
545 if (queue_idle_result != VK_SUCCESS) {
547 "Fatal : VkResult is \"{}\" in {} at line {}",
548 static_cast<int>(queue_idle_result),
554 if (descriptorPool != VK_NULL_HANDLE) {
560 void VK_Text::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
561 VkMemoryPropertyFlags properties, VkBuffer &buffer,
562 VkDeviceMemory &bufferMemory) {
563 VkBuffer newBuffer = VK_NULL_HANDLE;
564 VkDeviceMemory newMemory = VK_NULL_HANDLE;
566 VkBufferCreateInfo bufferInfo{};
567 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
568 bufferInfo.size = size;
569 bufferInfo.usage = usage;
570 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
573 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo,
nullptr, &newBuffer));
575 VkMemoryRequirements memRequirements;
576 vkGetBufferMemoryRequirements(device, newBuffer, &memRequirements);
578 VkMemoryAllocateInfo allocInfo{};
579 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
580 allocInfo.allocationSize = memRequirements.size;
581 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
582 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
585 if (newBuffer != VK_NULL_HANDLE) {
586 vkDestroyBuffer(device, newBuffer,
nullptr);
588 if (newMemory != VK_NULL_HANDLE) {
589 vkFreeMemory(device, newMemory,
nullptr);
594 if (buffer != VK_NULL_HANDLE) {
595 vkDestroyBuffer(device, buffer,
nullptr);
597 if (bufferMemory != VK_NULL_HANDLE) {
598 vkFreeMemory(device, bufferMemory,
nullptr);
601 bufferMemory = newMemory;
609 if (text.empty() || !textFont) {
616 return TTF_GetStringSize(textFont, text.c_str(), 0, &width, &height);
623 uint32_t VK_Text::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
624 VkPhysicalDeviceMemoryProperties memProperties;
625 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
627 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
628 if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
633 throw mxvk::Exception(
"Failed to find suitable memory type!");
636 bool VK_Text::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) {
637 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
639 VkImageMemoryBarrier barrier{};
640 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
641 barrier.oldLayout = oldLayout;
642 barrier.newLayout = newLayout;
643 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
644 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
645 barrier.image = image;
646 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
647 barrier.subresourceRange.baseMipLevel = 0;
648 barrier.subresourceRange.levelCount = 1;
649 barrier.subresourceRange.baseArrayLayer = 0;
650 barrier.subresourceRange.layerCount = 1;
652 VkPipelineStageFlags sourceStage;
653 VkPipelineStageFlags destinationStage;
655 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
656 barrier.srcAccessMask = 0;
657 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
658 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
659 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
660 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
661 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
662 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
663 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
664 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
666 throw std::invalid_argument(
"unsupported layout transition!");
669 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
670 return endSingleTimeCommands(commandBuffer);
673 bool VK_Text::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) {
674 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
676 VkBufferImageCopy region{};
677 region.bufferOffset = 0;
678 region.bufferRowLength = 0;
679 region.bufferImageHeight = 0;
680 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
681 region.imageSubresource.mipLevel = 0;
682 region.imageSubresource.baseArrayLayer = 0;
683 region.imageSubresource.layerCount = 1;
684 region.imageOffset = {0, 0, 0};
685 region.imageExtent = {width, height, 1};
687 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
689 return endSingleTimeCommands(commandBuffer);
692 VkCommandBuffer VK_Text::beginSingleTimeCommands() {
693 VkCommandBufferAllocateInfo allocInfo{};
694 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
695 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
696 allocInfo.commandPool = commandPool;
697 allocInfo.commandBufferCount = 1;
699 VkCommandBuffer commandBuffer;
700 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
702 VkCommandBufferBeginInfo beginInfo{};
703 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
704 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
708 return commandBuffer;
711 bool VK_Text::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
714 VkSubmitInfo submitInfo{};
715 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
716 submitInfo.commandBufferCount = 1;
717 submitInfo.pCommandBuffers = &commandBuffer;
719 const VkResult submitResult = vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
720 if (submitResult == VK_ERROR_DEVICE_LOST) {
721 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
722 std::cerr <<
"mxvk: Device lost during text command submit; skipping upload\n";
727 const VkResult waitResult = vkQueueWaitIdle(graphicsQueue);
728 if (waitResult == VK_ERROR_DEVICE_LOST) {
729 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
730 std::cerr <<
"mxvk: Device lost while waiting text queue idle; skipping upload\n";
735 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
739 void VK_Text::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling,
740 VkImageUsageFlags usage, VkMemoryPropertyFlags properties,
741 VkImage &image, VkDeviceMemory &imageMemory) {
742 VkImage newImage = VK_NULL_HANDLE;
743 VkDeviceMemory newMemory = VK_NULL_HANDLE;
745 VkImageCreateInfo imageInfo{};
746 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
747 imageInfo.imageType = VK_IMAGE_TYPE_2D;
748 imageInfo.extent.width = width;
749 imageInfo.extent.height = height;
750 imageInfo.extent.depth = 1;
751 imageInfo.mipLevels = 1;
752 imageInfo.arrayLayers = 1;
753 imageInfo.format = format;
754 imageInfo.tiling = tiling;
755 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
756 imageInfo.usage = usage;
757 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
758 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
761 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo,
nullptr, &newImage));
763 VkMemoryRequirements memRequirements;
764 vkGetImageMemoryRequirements(device, newImage, &memRequirements);
766 VkMemoryAllocateInfo allocInfo{};
767 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
768 allocInfo.allocationSize = memRequirements.size;
769 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
770 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
773 if (newImage != VK_NULL_HANDLE) {
774 vkDestroyImage(device, newImage,
nullptr);
776 if (newMemory != VK_NULL_HANDLE) {
777 vkFreeMemory(device, newMemory,
nullptr);
782 if (image != VK_NULL_HANDLE) {
783 vkDestroyImage(device, image,
nullptr);
785 if (imageMemory != VK_NULL_HANDLE) {
786 vkFreeMemory(device, imageMemory,
nullptr);
789 imageMemory = newMemory;
Small RAII wrapper for an SDL_ttf font handle.
TTF_Font * get() const noexcept
Font & operator=(const Font &)=delete
VkSampler fontSampler
Sampler used for all text textures.
void setFont(const std::string &fontPath, int fontSize)
Change the active font.
bool getTextDimensions(const std::string &text, int &width, int &height)
Measure the pixel dimensions of a string with the current font.
void clearQueue()
Discard all pending text quads without rendering them.
void clearCache()
Destroy all cached text textures, freeing GPU memory.
void renderText(VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout, uint32_t screenWidth, uint32_t screenHeight)
Record all queued text quads into a command buffer.
~VK_Text()
Destructor – destroys all Vulkan and SDL_ttf resources.
VK_Text(VkDevice device, VkPhysicalDevice physicalDevice, VkQueue graphicsQueue, VkCommandPool commandPool, const std::string &fontPath, int fontSize=24)
Construct VKText and load the font.
void printTextG_Solid(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for solid (opaque) rendering.
#define VK_CHECK_RESULT(f)
Vulkan SDL_ttf text renderer.
Utilities for loading and saving PNG images.
GPU texture resources cached for a rendered text string.
VkDeviceMemory imageMemory