10 Font::Font(
const std::string &fontPath,
int fontSize) {
reset(fontPath, fontSize); }
14 Font::Font(
Font &&other) noexcept : font(std::exchange(other.font,
nullptr)), font_path(std::move(other.font_path)), font_size(std::exchange(other.font_size, 0)), ownsTtfInit(std::exchange(other.ownsTtfInit,
false)) {}
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);
28 if (font !=
nullptr) {
41 if (fontPath.empty() || fontSize <= 0) {
42 throw mxvk::Exception(
"Font requires a non-empty path and positive font size");
48 throw mxvk::Exception(
"Failed to initialize SDL_ttf: " + std::string(SDL_GetError()));
51 TTF_Font *newFont = TTF_OpenFont(fontPath.c_str(), fontSize);
52 if (newFont ==
nullptr) {
54 throw mxvk::Exception(
"Failed to load font: " + std::string(SDL_GetError()));
63 VK_Text::VK_Text(VkDevice dev, VkPhysicalDevice physDev, VkQueue gQueue, VkCommandPool cmdPool,
const std::string &fontPath,
int fontSize) : device(dev), physicalDevice(physDev), graphicsQueue(gQueue), commandPool(cmdPool) {
66 throw mxvk::Exception(
"Failed to initialize SDL_ttf: " + std::string(SDL_GetError()));
69 initFont(fontPath, fontSize);
73 vkDeviceWaitIdle(device);
77 if (descriptorPool != VK_NULL_HANDLE) {
78 std::cout <<
"vk: destroying text descriptor pool\n";
79 vkDestroyDescriptorPool(device, descriptorPool,
nullptr);
83 std::cout <<
"vk: destroying text font sampler\n";
88 std::cout <<
"mxvk: closing text font\n";
95 for (
auto &[key, cached] : textureCache) {
96 destroyCachedTexture(cached);
102 if (cached.
imageView != VK_NULL_HANDLE) {
103 vkDestroyImageView(device, cached.
imageView,
nullptr);
106 if (cached.
image != VK_NULL_HANDLE) {
107 vkDestroyImage(device, cached.
image,
nullptr);
108 cached.
image = VK_NULL_HANDLE;
116 void VK_Text::pruneCache() {
117 if (textureCache.size() <= MAX_CACHED_TEXTURES) {
121 std::vector<std::pair<uint64_t, CacheKey>> entries;
122 entries.reserve(textureCache.size());
123 for (
const auto &[key, cached] : textureCache) {
127 const size_t removeCount = textureCache.size() - MAX_CACHED_TEXTURES;
128 std::nth_element(entries.begin(), entries.begin() +
static_cast<std::ptrdiff_t
>(removeCount), entries.end(), [](
const auto &lhs,
const auto &rhs) { return lhs.first < rhs.first; });
130 for (
size_t i = 0; i < removeCount; ++i) {
131 auto it = textureCache.find(entries[i].second);
132 if (it == textureCache.end()) {
135 destroyCachedTexture(it->second);
136 textureCache.erase(it);
140 void VK_Text::createDescriptorPool() { createDescriptorPool(maxPoolSets); }
142 void VK_Text::createDescriptorPool(uint32_t maxSets) {
143 VkDescriptorPoolSize poolSize{};
144 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
145 poolSize.descriptorCount = maxSets;
147 VkDescriptorPoolCreateInfo poolInfo{};
148 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
149 poolInfo.poolSizeCount = 1;
150 poolInfo.pPoolSizes = &poolSize;
151 poolInfo.maxSets = maxSets;
152 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
154 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo,
nullptr, &descriptorPool));
155 maxPoolSets = maxSets;
158 void VK_Text::growDescriptorPool() {
159 vkDeviceWaitIdle(device);
161 if (descriptorPool != VK_NULL_HANDLE) {
162 vkDestroyDescriptorPool(device, descriptorPool,
nullptr);
163 descriptorPool = VK_NULL_HANDLE;
166 createDescriptorPool(maxPoolSets);
169 VkDescriptorSet VK_Text::createDescriptorSet(VkImageView imageView) {
170 if (descriptorSetLayout == VK_NULL_HANDLE) {
171 throw mxvk::Exception(
"VKText::createDescriptorSet called before setDescriptorSetLayout");
174 if (descriptorPool == VK_NULL_HANDLE) {
175 createDescriptorPool();
178 VkDescriptorSetAllocateInfo allocInfo{};
179 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
180 allocInfo.descriptorPool = descriptorPool;
181 allocInfo.descriptorSetCount = 1;
182 allocInfo.pSetLayouts = &descriptorSetLayout;
184 VkDescriptorSet descriptorSet;
185 VkResult res = vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet);
186 if (res == VK_ERROR_OUT_OF_POOL_MEMORY || res == VK_ERROR_FRAGMENTED_POOL) {
187 growDescriptorPool();
188 allocInfo.descriptorPool = descriptorPool;
189 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
190 }
else if (res != VK_SUCCESS) {
191 throw mxvk::Exception(std::format(
"Fatal : VkResult is \"{}\" in {} at line {}",
static_cast<int>(res), __FILE__, __LINE__));
194 VkDescriptorImageInfo imageInfo{};
195 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
196 imageInfo.imageView = imageView;
199 VkWriteDescriptorSet descriptorWrite{};
200 descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
201 descriptorWrite.dstSet = descriptorSet;
202 descriptorWrite.dstBinding = 0;
203 descriptorWrite.dstArrayElement = 0;
204 descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
205 descriptorWrite.descriptorCount = 1;
206 descriptorWrite.pImageInfo = &imageInfo;
208 vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0,
nullptr);
210 return descriptorSet;
213 void VK_Text::initSampler() {
214 VkSamplerCreateInfo samplerInfo{};
215 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
216 samplerInfo.magFilter = VK_FILTER_LINEAR;
217 samplerInfo.minFilter = VK_FILTER_LINEAR;
218 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
219 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
220 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
221 samplerInfo.anisotropyEnable = VK_FALSE;
222 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
223 samplerInfo.unnormalizedCoordinates = VK_FALSE;
224 samplerInfo.compareEnable = VK_FALSE;
225 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
226 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
231 void VK_Text::initFont(
const std::string &fontPath,
int fontSize) {
232 font = TTF_OpenFont(fontPath.c_str(), fontSize);
234 throw mxvk::Exception(
"Failed to load font: " + std::string(SDL_GetError()));
239 std::cout << std::format(
"mxvk: Font loaded: {} @ {}pt\n", fontPath, fontSize);
243 vkDeviceWaitIdle(device);
254 initFont(fontPath, fontSize);
257 SDL_Surface *VK_Text::convertToRGBA(SDL_Surface *
surface) {
259 SDL_Surface *converted = SDL_ConvertSurface(
surface, SDL_PIXELFORMAT_RGBA32);
263 VkImageView VK_Text::createImageView(VkImage image, VkFormat format) {
264 VkImageViewCreateInfo viewInfo{};
265 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
266 viewInfo.image = image;
267 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
268 viewInfo.format = format;
269 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
270 viewInfo.subresourceRange.baseMipLevel = 0;
271 viewInfo.subresourceRange.levelCount = 1;
272 viewInfo.subresourceRange.baseArrayLayer = 0;
273 viewInfo.subresourceRange.layerCount = 1;
275 VkImageView imageView;
276 VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo,
nullptr, &imageView));
280 void VK_Text::printTextG_Solid(
const std::string &text,
int x,
int y,
const SDL_Color &col) { printTextG_SolidWithFont(text, x, y, col, font); }
282 void VK_Text::printTextG_Solid(
const std::string &text,
int x,
int y,
const SDL_Color &col, TTF_Font *textFont) { printTextG_SolidWithFont(text, x, y, col, textFont); }
284 void VK_Text::printTextG_Solid(
const std::string &text,
int x,
int y,
const SDL_Color &col,
const Font &textFont) { printTextG_SolidWithFont(text, x, y, col, textFont.
get()); }
286 void VK_Text::printTextG_SolidWithFont(
const std::string &text,
int x,
int y,
const SDL_Color &col, TTF_Font *textFont) {
287 if (text.empty() || !textFont)
295 quad.device = device;
300 quad.alpha =
static_cast<float>(col.a) / 255.0f;
301 quad.ownsTexture =
false;
303 SDL_Color textureColor = col;
304 textureColor.a = 255;
305 CacheKey key{text, textFont, textureColor.r, textureColor.g, textureColor.b, textureColor.a};
306 auto it = textureCache.find(key);
308 if (it != textureCache.end()) {
310 auto &cached = it->second;
312 quad.textImage = cached.
image;
315 quad.width = cached.
width;
316 quad.height = cached.
height;
320 SDL_Surface *textSurface = TTF_RenderText_Blended(textFont, text.c_str(), 0, textureColor);
325 SDL_Surface *rgbaSurface = convertToRGBA(textSurface);
326 SDL_DestroySurface(textSurface);
331 quad.width = rgbaSurface->w;
332 quad.height = rgbaSurface->h;
334 VkImage uploadedImage = VK_NULL_HANDLE;
335 VkDeviceMemory uploadedImageMemory = VK_NULL_HANDLE;
336 VkImageView uploadedImageView = VK_NULL_HANDLE;
337 VkBuffer stagingBuffer = VK_NULL_HANDLE;
338 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
339 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(rgbaSurface->w) *
static_cast<VkDeviceSize
>(rgbaSurface->h) * 4u;
341 auto cleanupUploadResources = [&]() {
342 if (stagingBuffer != VK_NULL_HANDLE) {
343 vkDestroyBuffer(device, stagingBuffer,
nullptr);
344 stagingBuffer = VK_NULL_HANDLE;
346 if (stagingMemory != VK_NULL_HANDLE) {
347 vkFreeMemory(device, stagingMemory,
nullptr);
348 stagingMemory = VK_NULL_HANDLE;
350 if (uploadedImageView != VK_NULL_HANDLE) {
351 vkDestroyImageView(device, uploadedImageView,
nullptr);
352 uploadedImageView = VK_NULL_HANDLE;
354 if (uploadedImage != VK_NULL_HANDLE) {
355 vkDestroyImage(device, uploadedImage,
nullptr);
356 uploadedImage = VK_NULL_HANDLE;
358 if (uploadedImageMemory != VK_NULL_HANDLE) {
359 vkFreeMemory(device, uploadedImageMemory,
nullptr);
360 uploadedImageMemory = VK_NULL_HANDLE;
364 bool uploadSucceeded =
false;
366 createImage(rgbaSurface->w, rgbaSurface->h, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, uploadedImage, uploadedImageMemory);
368 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
370 void *data =
nullptr;
371 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
372 memcpy(data, rgbaSurface->pixels,
static_cast<size_t>(imageSize));
373 vkUnmapMemory(device, stagingMemory);
375 if (!transitionImageLayout(uploadedImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)) {
376 SDL_DestroySurface(rgbaSurface);
377 cleanupUploadResources();
380 if (!copyBufferToImage(stagingBuffer, uploadedImage,
static_cast<uint32_t
>(rgbaSurface->w),
static_cast<uint32_t
>(rgbaSurface->h))) {
381 SDL_DestroySurface(rgbaSurface);
382 cleanupUploadResources();
385 if (!transitionImageLayout(uploadedImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)) {
386 SDL_DestroySurface(rgbaSurface);
387 cleanupUploadResources();
391 uploadedImageView = createImageView(uploadedImage, VK_FORMAT_R8G8B8A8_UNORM);
392 uploadSucceeded =
true;
393 }
catch (
const mxvk::Exception &ex) {
394 SDL_DestroySurface(rgbaSurface);
395 cleanupUploadResources();
396 static bool textTextureWarningLogged =
false;
397 if (!textTextureWarningLogged) {
398 std::cerr <<
"mxvk: dropping text texture after Vulkan allocation failure: " << ex.
text() <<
"\n";
399 textTextureWarningLogged =
true;
404 SDL_DestroySurface(rgbaSurface);
406 if (!uploadSucceeded) {
407 cleanupUploadResources();
411 if (stagingBuffer != VK_NULL_HANDLE) {
412 vkDestroyBuffer(device, stagingBuffer,
nullptr);
413 stagingBuffer = VK_NULL_HANDLE;
415 if (stagingMemory != VK_NULL_HANDLE) {
416 vkFreeMemory(device, stagingMemory,
nullptr);
417 stagingMemory = VK_NULL_HANDLE;
420 quad.textImage = uploadedImage;
421 quad.textImageMemory = uploadedImageMemory;
422 quad.textImageView = uploadedImageView;
425 textureCache[key] = {quad.textImage, quad.textImageMemory, quad.textImageView, quad.width, quad.height, ++cacheUseSerial};
431 float x1 = x0 + quad.width;
432 float y1 = y0 + quad.height;
434 TextVertex v0 = {{x0, y0}, {0.0f, 0.0f}};
435 TextVertex v1 = {{x1, y0}, {1.0f, 0.0f}};
436 TextVertex v2 = {{x1, y1}, {1.0f, 1.0f}};
437 TextVertex v3 = {{x0, y1}, {0.0f, 1.0f}};
439 quad.vertices = {v0, v1, v2, v3};
440 quad.indices = {0, 1, 2, 0, 2, 3};
445 VkDeviceSize vertexSize = quad.vertices.size() *
sizeof(TextVertex);
446 createBuffer(vertexSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, quad.vertexBuffer, quad.vertexBufferMemory);
448 VK_CHECK_RESULT(vkMapMemory(device, quad.vertexBufferMemory, 0, vertexSize, 0, &data));
449 memcpy(data, quad.vertices.data(), vertexSize);
450 vkUnmapMemory(device, quad.vertexBufferMemory);
452 VkDeviceSize indexSize = quad.indices.size() *
sizeof(uint16_t);
453 createBuffer(indexSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, quad.indexBuffer, quad.indexBufferMemory);
455 VK_CHECK_RESULT(vkMapMemory(device, quad.indexBufferMemory, 0, indexSize, 0, &data));
456 memcpy(data, quad.indices.data(), indexSize);
457 vkUnmapMemory(device, quad.indexBufferMemory);
459 quad.descriptorSet = createDescriptorSet(quad.textImageView);
460 }
catch (
const mxvk::Exception &ex) {
461 static bool textAllocationWarningLogged =
false;
462 if (!textAllocationWarningLogged) {
463 std::cerr <<
"mxvk: dropping text draw after Vulkan allocation failure: " << ex.
text() <<
"\n";
464 textAllocationWarningLogged =
true;
469 textQuads.emplace_back(std::move(quad));
472 void VK_Text::renderText(VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout, uint32_t screenWidth, uint32_t screenHeight) {
474 struct TextPushConstants {
479 } pc{
static_cast<float>(screenWidth),
static_cast<float>(screenHeight), 1.0f, 0.0f};
481 for (
auto &quad : textQuads) {
482 pc.alpha = quad.alpha;
483 vkCmdPushConstants(cmdBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(TextPushConstants), &pc);
484 VkBuffer vertexBuffers[] = {quad.vertexBuffer};
485 VkDeviceSize offsets[] = {0};
486 vkCmdBindVertexBuffers(cmdBuffer, 0, 1, vertexBuffers, offsets);
487 vkCmdBindIndexBuffer(cmdBuffer, quad.indexBuffer, 0, VK_INDEX_TYPE_UINT16);
488 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &quad.descriptorSet, 0,
nullptr);
490 vkCmdDrawIndexed(cmdBuffer, quad.indexCount, 1, 0, 0, 0);
495 if (textQuads.empty()) {
498 if (device == VK_NULL_HANDLE) {
505 const VkResult queue_idle_result = vkQueueWaitIdle(graphicsQueue);
506 if (queue_idle_result == VK_ERROR_DEVICE_LOST) {
507 std::cerr <<
"mxvk: Device lost while clearing queue; skipping text resource reset\n";
511 if (queue_idle_result != VK_SUCCESS) {
512 throw mxvk::Exception(std::format(
"Fatal : VkResult is \"{}\" in {} at line {}",
static_cast<int>(queue_idle_result), __FILE__, __LINE__));
516 if (descriptorPool != VK_NULL_HANDLE) {
522 void VK_Text::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory) {
523 VkBuffer newBuffer = VK_NULL_HANDLE;
524 VkDeviceMemory newMemory = VK_NULL_HANDLE;
526 VkBufferCreateInfo bufferInfo{};
527 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
528 bufferInfo.size = size;
529 bufferInfo.usage = usage;
530 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
533 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo,
nullptr, &newBuffer));
535 VkMemoryRequirements memRequirements;
536 vkGetBufferMemoryRequirements(device, newBuffer, &memRequirements);
538 VkMemoryAllocateInfo allocInfo{};
539 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
540 allocInfo.allocationSize = memRequirements.size;
541 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
542 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
545 if (newBuffer != VK_NULL_HANDLE) {
546 vkDestroyBuffer(device, newBuffer,
nullptr);
548 if (newMemory != VK_NULL_HANDLE) {
549 vkFreeMemory(device, newMemory,
nullptr);
554 if (buffer != VK_NULL_HANDLE) {
555 vkDestroyBuffer(device, buffer,
nullptr);
557 if (bufferMemory != VK_NULL_HANDLE) {
558 vkFreeMemory(device, bufferMemory,
nullptr);
561 bufferMemory = newMemory;
567 if (text.empty() || !textFont) {
574 return TTF_GetStringSize(textFont, text.c_str(), 0, &width, &height);
579 uint32_t VK_Text::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
580 VkPhysicalDeviceMemoryProperties memProperties;
581 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
583 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
584 if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
589 throw mxvk::Exception(
"Failed to find suitable memory type!");
592 bool VK_Text::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) {
593 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
595 VkImageMemoryBarrier barrier{};
596 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
597 barrier.oldLayout = oldLayout;
598 barrier.newLayout = newLayout;
599 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
600 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
601 barrier.image = image;
602 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
603 barrier.subresourceRange.baseMipLevel = 0;
604 barrier.subresourceRange.levelCount = 1;
605 barrier.subresourceRange.baseArrayLayer = 0;
606 barrier.subresourceRange.layerCount = 1;
608 VkPipelineStageFlags sourceStage;
609 VkPipelineStageFlags destinationStage;
611 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
612 barrier.srcAccessMask = 0;
613 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
614 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
615 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
616 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
617 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
618 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
619 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
620 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
622 throw std::invalid_argument(
"unsupported layout transition!");
625 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
626 return endSingleTimeCommands(commandBuffer);
629 bool VK_Text::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) {
630 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
632 VkBufferImageCopy region{};
633 region.bufferOffset = 0;
634 region.bufferRowLength = 0;
635 region.bufferImageHeight = 0;
636 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
637 region.imageSubresource.mipLevel = 0;
638 region.imageSubresource.baseArrayLayer = 0;
639 region.imageSubresource.layerCount = 1;
640 region.imageOffset = {0, 0, 0};
641 region.imageExtent = {width, height, 1};
643 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
645 return endSingleTimeCommands(commandBuffer);
648 VkCommandBuffer VK_Text::beginSingleTimeCommands() {
649 VkCommandBufferAllocateInfo allocInfo{};
650 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
651 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
652 allocInfo.commandPool = commandPool;
653 allocInfo.commandBufferCount = 1;
655 VkCommandBuffer commandBuffer;
656 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
658 VkCommandBufferBeginInfo beginInfo{};
659 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
660 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
664 return commandBuffer;
667 bool VK_Text::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
670 VkSubmitInfo submitInfo{};
671 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
672 submitInfo.commandBufferCount = 1;
673 submitInfo.pCommandBuffers = &commandBuffer;
675 const VkResult submitResult = vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
676 if (submitResult == VK_ERROR_DEVICE_LOST) {
677 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
678 std::cerr <<
"mxvk: Device lost during text command submit; skipping upload\n";
683 const VkResult waitResult = vkQueueWaitIdle(graphicsQueue);
684 if (waitResult == VK_ERROR_DEVICE_LOST) {
685 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
686 std::cerr <<
"mxvk: Device lost while waiting text queue idle; skipping upload\n";
691 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
695 void VK_Text::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &imageMemory) {
696 VkImage newImage = VK_NULL_HANDLE;
697 VkDeviceMemory newMemory = VK_NULL_HANDLE;
699 VkImageCreateInfo imageInfo{};
700 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
701 imageInfo.imageType = VK_IMAGE_TYPE_2D;
702 imageInfo.extent.width = width;
703 imageInfo.extent.height = height;
704 imageInfo.extent.depth = 1;
705 imageInfo.mipLevels = 1;
706 imageInfo.arrayLayers = 1;
707 imageInfo.format = format;
708 imageInfo.tiling = tiling;
709 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
710 imageInfo.usage = usage;
711 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
712 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
715 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo,
nullptr, &newImage));
717 VkMemoryRequirements memRequirements;
718 vkGetImageMemoryRequirements(device, newImage, &memRequirements);
720 VkMemoryAllocateInfo allocInfo{};
721 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
722 allocInfo.allocationSize = memRequirements.size;
723 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
724 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
727 if (newImage != VK_NULL_HANDLE) {
728 vkDestroyImage(device, newImage,
nullptr);
730 if (newMemory != VK_NULL_HANDLE) {
731 vkFreeMemory(device, newMemory,
nullptr);
736 if (image != VK_NULL_HANDLE) {
737 vkDestroyImage(device, image,
nullptr);
739 if (imageMemory != VK_NULL_HANDLE) {
740 vkFreeMemory(device, imageMemory,
nullptr);
743 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