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 (device == VK_NULL_HANDLE) {
536 const VkResult queue_idle_result = vkQueueWaitIdle(graphicsQueue);
537 if (queue_idle_result == VK_ERROR_DEVICE_LOST) {
538 std::cerr <<
"mxvk: Device lost while clearing queue; skipping text resource reset\n";
542 if (queue_idle_result != VK_SUCCESS) {
544 "Fatal : VkResult is \"{}\" in {} at line {}",
545 static_cast<int>(queue_idle_result),
551 if (descriptorPool != VK_NULL_HANDLE) {
557 void VK_Text::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
558 VkMemoryPropertyFlags properties, VkBuffer &buffer,
559 VkDeviceMemory &bufferMemory) {
560 VkBuffer newBuffer = VK_NULL_HANDLE;
561 VkDeviceMemory newMemory = VK_NULL_HANDLE;
563 VkBufferCreateInfo bufferInfo{};
564 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
565 bufferInfo.size = size;
566 bufferInfo.usage = usage;
567 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
570 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo,
nullptr, &newBuffer));
572 VkMemoryRequirements memRequirements;
573 vkGetBufferMemoryRequirements(device, newBuffer, &memRequirements);
575 VkMemoryAllocateInfo allocInfo{};
576 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
577 allocInfo.allocationSize = memRequirements.size;
578 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
579 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
582 if (newBuffer != VK_NULL_HANDLE) {
583 vkDestroyBuffer(device, newBuffer,
nullptr);
585 if (newMemory != VK_NULL_HANDLE) {
586 vkFreeMemory(device, newMemory,
nullptr);
591 if (buffer != VK_NULL_HANDLE) {
592 vkDestroyBuffer(device, buffer,
nullptr);
594 if (bufferMemory != VK_NULL_HANDLE) {
595 vkFreeMemory(device, bufferMemory,
nullptr);
598 bufferMemory = newMemory;
606 if (text.empty() || !textFont) {
613 return TTF_GetStringSize(textFont, text.c_str(), 0, &width, &height);
620 uint32_t VK_Text::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
621 VkPhysicalDeviceMemoryProperties memProperties;
622 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
624 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
625 if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
630 throw mxvk::Exception(
"Failed to find suitable memory type!");
633 bool VK_Text::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) {
634 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
636 VkImageMemoryBarrier barrier{};
637 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
638 barrier.oldLayout = oldLayout;
639 barrier.newLayout = newLayout;
640 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
641 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
642 barrier.image = image;
643 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
644 barrier.subresourceRange.baseMipLevel = 0;
645 barrier.subresourceRange.levelCount = 1;
646 barrier.subresourceRange.baseArrayLayer = 0;
647 barrier.subresourceRange.layerCount = 1;
649 VkPipelineStageFlags sourceStage;
650 VkPipelineStageFlags destinationStage;
652 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
653 barrier.srcAccessMask = 0;
654 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
655 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
656 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
657 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
658 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
659 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
660 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
661 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
663 throw std::invalid_argument(
"unsupported layout transition!");
666 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
667 return endSingleTimeCommands(commandBuffer);
670 bool VK_Text::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) {
671 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
673 VkBufferImageCopy region{};
674 region.bufferOffset = 0;
675 region.bufferRowLength = 0;
676 region.bufferImageHeight = 0;
677 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
678 region.imageSubresource.mipLevel = 0;
679 region.imageSubresource.baseArrayLayer = 0;
680 region.imageSubresource.layerCount = 1;
681 region.imageOffset = {0, 0, 0};
682 region.imageExtent = {width, height, 1};
684 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
686 return endSingleTimeCommands(commandBuffer);
689 VkCommandBuffer VK_Text::beginSingleTimeCommands() {
690 VkCommandBufferAllocateInfo allocInfo{};
691 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
692 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
693 allocInfo.commandPool = commandPool;
694 allocInfo.commandBufferCount = 1;
696 VkCommandBuffer commandBuffer;
697 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
699 VkCommandBufferBeginInfo beginInfo{};
700 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
701 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
705 return commandBuffer;
708 bool VK_Text::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
711 VkSubmitInfo submitInfo{};
712 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
713 submitInfo.commandBufferCount = 1;
714 submitInfo.pCommandBuffers = &commandBuffer;
716 const VkResult submitResult = vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
717 if (submitResult == VK_ERROR_DEVICE_LOST) {
718 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
719 std::cerr <<
"mxvk: Device lost during text command submit; skipping upload\n";
724 const VkResult waitResult = vkQueueWaitIdle(graphicsQueue);
725 if (waitResult == VK_ERROR_DEVICE_LOST) {
726 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
727 std::cerr <<
"mxvk: Device lost while waiting text queue idle; skipping upload\n";
732 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
736 void VK_Text::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling,
737 VkImageUsageFlags usage, VkMemoryPropertyFlags properties,
738 VkImage &image, VkDeviceMemory &imageMemory) {
739 VkImage newImage = VK_NULL_HANDLE;
740 VkDeviceMemory newMemory = VK_NULL_HANDLE;
742 VkImageCreateInfo imageInfo{};
743 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
744 imageInfo.imageType = VK_IMAGE_TYPE_2D;
745 imageInfo.extent.width = width;
746 imageInfo.extent.height = height;
747 imageInfo.extent.depth = 1;
748 imageInfo.mipLevels = 1;
749 imageInfo.arrayLayers = 1;
750 imageInfo.format = format;
751 imageInfo.tiling = tiling;
752 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
753 imageInfo.usage = usage;
754 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
755 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
758 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo,
nullptr, &newImage));
760 VkMemoryRequirements memRequirements;
761 vkGetImageMemoryRequirements(device, newImage, &memRequirements);
763 VkMemoryAllocateInfo allocInfo{};
764 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
765 allocInfo.allocationSize = memRequirements.size;
766 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
767 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
770 if (newImage != VK_NULL_HANDLE) {
771 vkDestroyImage(device, newImage,
nullptr);
773 if (newMemory != VK_NULL_HANDLE) {
774 vkFreeMemory(device, newMemory,
nullptr);
779 if (image != VK_NULL_HANDLE) {
780 vkDestroyImage(device, image,
nullptr);
782 if (imageMemory != VK_NULL_HANDLE) {
783 vkFreeMemory(device, imageMemory,
nullptr);
786 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