libmx2 2.2.1
C++20 multimedia and game-engine library (SDL2 / OpenGL / Vulkan)
Loading...
Searching...
No Matches
vk_text.hpp
Go to the documentation of this file.
1/**
2 * @file vk_text.hpp
3 * @brief Vulkan SDL_ttf text renderer.
4 *
5 * VKText rasterises text strings into SDL surfaces, uploads them as
6 * Vulkan image textures, and batches the resulting quads for submission
7 * during the render pass. Each printTextG_Solid() call adds one TextQuad
8 * to the pending queue, which is flushed by renderText().
9 */
10#ifndef __MXTEXT__
11#define __MXTEXT__
12
13#include"config.h"
14
15#ifdef WITH_MOLTEN
16#include<vulkan/vulkan.h>
17#else
18#include"volk.h"
19#endif
20
21#include"exception.hpp"
22#include <SDL_vulkan.h>
23#include <SDL_ttf.h>
24#include <iostream>
25#include <stdexcept>
26#include <cstdlib>
27#include <vector>
28#include <string>
29#include <cstring>
30#include <unordered_map>
31
32#ifndef VK_CHECK_RESULT
33#define VK_CHECK_RESULT(f) { \
34 VkResult res = (f); \
35 if (res != VK_SUCCESS) { \
36 throw mx::Exception("Fatal : VkResult is \"" + std::to_string(res) + "\" in " + __FILE__ + " at line " + std::to_string(__LINE__)); \
37 } \
38}
39#endif
40
41namespace mx {
42
43/**
44 * @class VKText
45 * @brief Renders SDL_ttf text into Vulkan image textures.
46 *
47 * Loads a TrueType font via SDL_ttf, creates a descriptor pool for per-glyph
48 * textures, and provides a print-then-render workflow:
49 * 1. Call printTextG_Solid() for each string to display.
50 * 2. Call renderText() once during the render pass to draw all queued strings.
51 * 3. Optionally call clearQueue() to discard pending strings.
52 */
53 /// Cache key combining text content and colour.
54 struct CacheKey {
55 std::string text;
56 uint8_t r, g, b, a;
57 bool operator==(const CacheKey &other) const {
58 return text == other.text && r == other.r && g == other.g && b == other.b && a == other.a;
59 }
60 };
61
62 /// Hash functor for CacheKey.
63 struct CacheKeyHash {
64 size_t operator()(const CacheKey &k) const {
65 size_t h = std::hash<std::string>{}(k.text);
66 h ^= std::hash<uint8_t>{}(k.r) + 0x9e3779b9 + (h << 6) + (h >> 2);
67 h ^= std::hash<uint8_t>{}(k.g) + 0x9e3779b9 + (h << 6) + (h >> 2);
68 h ^= std::hash<uint8_t>{}(k.b) + 0x9e3779b9 + (h << 6) + (h >> 2);
69 h ^= std::hash<uint8_t>{}(k.a) + 0x9e3779b9 + (h << 6) + (h >> 2);
70 return h;
71 }
72 };
73
74 /// GPU texture resources cached for a rendered text string.
76 VkImage image = VK_NULL_HANDLE;
77 VkDeviceMemory imageMemory = VK_NULL_HANDLE;
78 VkImageView imageView = VK_NULL_HANDLE;
79 int width = 0;
80 int height = 0;
81 };
82
83 class VKText {
84 public:
85 /**
86 * @brief Construct VKText and load the font.
87 * @param device Logical device.
88 * @param physicalDevice Physical device.
89 * @param graphicsQueue Graphics queue.
90 * @param commandPool Command pool for staging.
91 * @param fontPath Path to the TTF font file.
92 * @param fontSize Point size.
93 */
94 VKText(VkDevice device, VkPhysicalDevice physicalDevice, VkQueue graphicsQueue,
95 VkCommandPool commandPool, const std::string &fontPath, int fontSize = 24);
96
97 /** @brief Destructor -- destroys all Vulkan and SDL_ttf resources. */
98 ~VKText();
99
100 VKText(const VKText&) = delete;
101 VKText& operator=(const VKText&) = delete;
102 VKText(VKText&&) = delete;
103 VKText& operator=(VKText&&) = delete;
104
105 /**
106 * @brief Queue a text string for solid (opaque) rendering.
107 * @param text String to draw.
108 * @param x Destination X in pixels.
109 * @param y Destination Y in pixels.
110 * @param col Text colour.
111 */
112 void printTextG_Solid(const std::string &text, int x, int y, const SDL_Color &col);
113
114 /**
115 * @brief Record all queued text quads into a command buffer.
116 * @param cmdBuffer Active Vulkan command buffer.
117 * @param pipelineLayout Pipeline layout for push constants.
118 * @param screenWidth Viewport width.
119 * @param screenHeight Viewport height.
120 */
121 void renderText(VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout,
122 uint32_t screenWidth, uint32_t screenHeight);
123
124 /** @brief Discard all pending text quads without rendering them. */
125 void clearQueue();
126
127 /** @brief Assign an external descriptor-set layout. */
128 void setDescriptorSetLayout(VkDescriptorSetLayout layout) { descriptorSetLayout = layout; }
129
130 /**
131 * @brief Change the active font.
132 * @param fontPath New font file path.
133 * @param fontSize New point size.
134 */
135 void setFont(const std::string &fontPath, int fontSize);
136
137 /** @brief Destroy all cached text textures, freeing GPU memory. */
138 void clearCache();
139
140 /**
141 * @brief Measure the pixel dimensions of a string with the current font.
142 * @param text String to measure.
143 * @param width Output: pixel width.
144 * @param height Output: pixel height.
145 * @return @c true if measurement succeeded.
146 */
147 bool getTextDimensions(const std::string &text, int& width, int& height);
148
149 VkSampler fontSampler = VK_NULL_HANDLE; ///< Sampler used for all text textures.
150 private:
151 struct TextVertex {
152 float pos[2];
153 float texCoord[2];
154 };
155
156 struct TextQuad {
157 std::string text;
158 int x, y;
160 SDL_Color color;
161 std::vector<TextVertex> vertices;
162 std::vector<uint16_t> indices;
163 VkBuffer vertexBuffer = VK_NULL_HANDLE;
164 VkDeviceMemory vertexBufferMemory = VK_NULL_HANDLE;
165 VkBuffer indexBuffer = VK_NULL_HANDLE;
166 VkDeviceMemory indexBufferMemory = VK_NULL_HANDLE;
167 VkImage textImage = VK_NULL_HANDLE;
168 VkDeviceMemory textImageMemory = VK_NULL_HANDLE;
169 VkImageView textImageView = VK_NULL_HANDLE;
170 VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
171 uint32_t indexCount = 0;
172 VkDevice device = VK_NULL_HANDLE;
173 bool ownsTexture = true; ///< false when texture is owned by the cache.
174
175 TextQuad() = default;
176 TextQuad(const TextQuad&) = delete;
177 TextQuad& operator=(const TextQuad&) = delete;
178 TextQuad(TextQuad&& other) noexcept {
179 *this = std::move(other);
180 }
181 TextQuad& operator=(TextQuad&& other) noexcept {
182 if (this != &other) {
183 text = std::move(other.text);
184 x = other.x;
185 y = other.y;
186 width = other.width;
187 height = other.height;
188 color = other.color;
189 vertices = std::move(other.vertices);
190 indices = std::move(other.indices);
191 vertexBuffer = other.vertexBuffer;
192 vertexBufferMemory = other.vertexBufferMemory;
193 indexBuffer = other.indexBuffer;
194 indexBufferMemory = other.indexBufferMemory;
195 textImage = other.textImage;
196 textImageMemory = other.textImageMemory;
197 textImageView = other.textImageView;
198 descriptorSet = other.descriptorSet;
199 indexCount = other.indexCount;
200 device = other.device;
201 ownsTexture = other.ownsTexture;
202
203 other.ownsTexture = false;
204 other.vertexBuffer = VK_NULL_HANDLE;
205 other.vertexBufferMemory = VK_NULL_HANDLE;
206 other.indexBuffer = VK_NULL_HANDLE;
207 other.indexBufferMemory = VK_NULL_HANDLE;
208 other.textImage = VK_NULL_HANDLE;
209 other.textImageMemory = VK_NULL_HANDLE;
210 other.textImageView = VK_NULL_HANDLE;
211 other.descriptorSet = VK_NULL_HANDLE;
212 other.indexCount = 0;
213 other.device = VK_NULL_HANDLE;
214 }
215 return *this;
216 }
217
219 if (device != VK_NULL_HANDLE) {
220 if (ownsTexture) {
221 if (textImageView != VK_NULL_HANDLE) {
222 vkDestroyImageView(device, textImageView, nullptr);
223 }
224 if (textImage != VK_NULL_HANDLE) {
225 vkDestroyImage(device, textImage, nullptr);
226 vkFreeMemory(device, textImageMemory, nullptr);
227 }
228 }
229 if (vertexBuffer != VK_NULL_HANDLE) {
230 vkDestroyBuffer(device, vertexBuffer, nullptr);
231 vkFreeMemory(device, vertexBufferMemory, nullptr);
232 }
233 if (indexBuffer != VK_NULL_HANDLE) {
234 vkDestroyBuffer(device, indexBuffer, nullptr);
235 vkFreeMemory(device, indexBufferMemory, nullptr);
236 }
237 }
238 }
239 };
240
241 VkDevice device = VK_NULL_HANDLE;
242 VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
243 VkQueue graphicsQueue = VK_NULL_HANDLE;
244 VkCommandPool commandPool = VK_NULL_HANDLE;
245
246 TTF_Font *font = nullptr;
247 std::vector<TextQuad> textQuads;
248 VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
249 VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
250 uint32_t maxPoolSets = 100;
251 std::unordered_map<CacheKey, CachedTexture, CacheKeyHash> textureCache;
252
253 void initFont(const std::string &fontPath, int fontSize);
255 void createDescriptorPool(uint32_t maxSets);
256 void growDescriptorPool();
257 VkDescriptorSet createDescriptorSet(VkImageView imageView);
258 void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
259 VkMemoryPropertyFlags properties, VkBuffer& buffer,
260 VkDeviceMemory& bufferMemory);
261 uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties);
262 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height);
263 VkCommandBuffer beginSingleTimeCommands();
264 void endSingleTimeCommands(VkCommandBuffer commandBuffer);
265 void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout);
266 void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling,
267 VkImageUsageFlags usage, VkMemoryPropertyFlags properties,
268 VkImage& image, VkDeviceMemory& imageMemory);
269 VkImageView createImageView(VkImage image, VkFormat format);
270 SDL_Surface* convertToRGBA(SDL_Surface* surface);
271 };
272}
273
274
275#endif
VkPhysicalDevice physicalDevice
Definition vk_text.hpp:242
void printTextG_Solid(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for solid (opaque) rendering.
Definition vk_text.cpp:190
VkDescriptorSetLayout descriptorSetLayout
Definition vk_text.hpp:249
VKText(VKText &&)=delete
VkDescriptorSet createDescriptorSet(VkImageView imageView)
Definition vk_text.cpp:83
TTF_Font * font
Definition vk_text.hpp:246
void initFont(const std::string &fontPath, int fontSize)
Definition vk_text.cpp:127
VkDevice device
Definition vk_text.hpp:241
void growDescriptorPool()
Definition vk_text.cpp:72
VkCommandBuffer beginSingleTimeCommands()
Definition vk_text.cpp:429
VKText(const VKText &)=delete
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
Definition vk_text.cpp:360
VkDescriptorPool descriptorPool
Definition vk_text.hpp:248
VKText & operator=(const VKText &)=delete
uint32_t maxPoolSets
Definition vk_text.hpp:250
VkSampler fontSampler
Sampler used for all text textures.
Definition vk_text.hpp:149
void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
Definition vk_text.cpp:324
void renderText(VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout, uint32_t screenWidth, uint32_t screenHeight)
Record all queued text quads into a command buffer.
Definition vk_text.cpp:296
void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &imageMemory)
Definition vk_text.cpp:462
~VKText()
Destructor – destroys all Vulkan and SDL_ttf resources.
Definition vk_text.cpp:20
VKText & operator=(VKText &&)=delete
void endSingleTimeCommands(VkCommandBuffer commandBuffer)
Definition vk_text.cpp:448
std::unordered_map< CacheKey, CachedTexture, CacheKeyHash > textureCache
Definition vk_text.hpp:251
SDL_Surface * convertToRGBA(SDL_Surface *surface)
Definition vk_text.cpp:165
void setDescriptorSetLayout(VkDescriptorSetLayout layout)
Assign an external descriptor-set layout.
Definition vk_text.hpp:128
void createDescriptorPool()
Definition vk_text.cpp:52
void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
Definition vk_text.cpp:410
void setFont(const std::string &fontPath, int fontSize)
Change the active font.
Definition vk_text.cpp:150
VkQueue graphicsQueue
Definition vk_text.hpp:243
void clearCache()
Destroy all cached text textures, freeing GPU memory.
Definition vk_text.cpp:39
VkImageView createImageView(VkImage image, VkFormat format)
Definition vk_text.cpp:173
VkCommandPool commandPool
Definition vk_text.hpp:244
void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
Definition vk_text.cpp:373
bool getTextDimensions(const std::string &text, int &width, int &height)
Measure the pixel dimensions of a string with the current font.
Definition vk_text.cpp:347
VKText(VkDevice device, VkPhysicalDevice physicalDevice, VkQueue graphicsQueue, VkCommandPool commandPool, const std::string &fontPath, int fontSize=24)
Construct VKText and load the font.
Definition vk_text.cpp:9
void clearQueue()
Discard all pending text quads without rendering them.
Definition vk_text.cpp:317
std::vector< TextQuad > textQuads
Definition vk_text.hpp:247
Exception type used throughout libmx.
Definition cfg.cpp:11
Hash functor for CacheKey.
Definition vk_text.hpp:63
size_t operator()(const CacheKey &k) const
Definition vk_text.hpp:64
Cache key combining text content and colour.
Definition vk_text.hpp:54
uint8_t b
Definition vk_text.hpp:56
uint8_t r
Definition vk_text.hpp:56
bool operator==(const CacheKey &other) const
Definition vk_text.hpp:57
uint8_t a
Definition vk_text.hpp:56
std::string text
Definition vk_text.hpp:55
uint8_t g
Definition vk_text.hpp:56
GPU texture resources cached for a rendered text string.
Definition vk_text.hpp:75
VkImageView imageView
Definition vk_text.hpp:78
VkDeviceMemory imageMemory
Definition vk_text.hpp:77
bool ownsTexture
false when texture is owned by the cache.
Definition vk_text.hpp:173
VkDescriptorSet descriptorSet
Definition vk_text.hpp:170
VkDeviceMemory vertexBufferMemory
Definition vk_text.hpp:164
TextQuad & operator=(const TextQuad &)=delete
VkDeviceMemory indexBufferMemory
Definition vk_text.hpp:166
TextQuad & operator=(TextQuad &&other) noexcept
Definition vk_text.hpp:181
VkImageView textImageView
Definition vk_text.hpp:169
TextQuad(TextQuad &&other) noexcept
Definition vk_text.hpp:178
VkDeviceMemory textImageMemory
Definition vk_text.hpp:168
std::vector< TextVertex > vertices
Definition vk_text.hpp:161
TextQuad(const TextQuad &)=delete
std::vector< uint16_t > indices
Definition vk_text.hpp:162