MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_sprite3d.cpp
Go to the documentation of this file.
2
3#include "mxvk/mxvk.hpp"
5#include "mxvk/mxvk_png.hpp"
7
8#include <algorithm>
9#include <array>
10#include <cstddef>
11#include <cstdint>
12#include <cstring>
13#include <filesystem>
14#include <format>
15#include <fstream>
16#include <iostream>
17
18#ifndef VK_CHECK_RESULT
19#define VK_CHECK_RESULT(f) \
20 { \
21 VkResult res = (f); \
22 if (res != VK_SUCCESS) { \
23 throw mxvk::Exception(std::format("Fatal : VkResult is \"{}\" in {} at line {}", static_cast<int>(res), __FILE__, __LINE__)); \
24 } \
25 }
26#endif
27
28namespace mxvk {
29
31
32 void VK_Sprite3D::load(VK_Window *window, const std::string &pngPath, const std::string &vertexPath, const std::string &fragmentPath) {
33 SDL_Surface *surface = mxvk::LoadPNG(pngPath.c_str());
34 if (surface == nullptr) {
35 throw mxvk::Exception("Failed to load 3D sprite image: " + pngPath);
36 }
37 load(window, surface, vertexPath, fragmentPath);
38 SDL_DestroySurface(surface);
39 std::cout << std::format("mxvk: Loaded 3D sprite PNG: {}\n", pngPath);
40 }
41
42 void VK_Sprite3D::load(VK_Window *window, SDL_Surface *surface, const std::string &vertexPath, const std::string &fragmentPath) {
43 if (window == nullptr) {
44 throw mxvk::Exception("VK_Sprite3D::load called with null window");
45 }
46 if (surface == nullptr) {
47 throw mxvk::Exception("VK_Sprite3D::load called with null surface");
48 }
49
50 cleanup();
51
52 device = window->getDevice();
53 physicalDevice = window->getPhysicalDevice();
54 graphicsQueue = window->getGraphicsQueue();
55 commandPool = window->getCommandPool();
56 pipelineCache = window->getPipelineCache();
57 colorAttachmentFormat = window->getSwapchainFormat();
58 depthAttachmentFormat = window->getDepthFormat();
59 imageCount = window->getSwapchainImageCount();
60 vertexShaderPath = vertexPath.empty() ? (std::filesystem::path(MXVK_SPRITE3D_SHADER_DIR) / "sprite3d.vert.spv").string() : vertexPath;
61 fragmentShaderPath = fragmentPath.empty() ? (std::filesystem::path(MXVK_SPRITE3D_SHADER_DIR) / "sprite3d.frag.spv").string() : fragmentPath;
62
63 if (device == VK_NULL_HANDLE || physicalDevice == VK_NULL_HANDLE || graphicsQueue == VK_NULL_HANDLE || commandPool == VK_NULL_HANDLE) {
64 throw mxvk::Exception("Cannot create 3D sprite before Vulkan render resources are available");
65 }
66 if (colorAttachmentFormat == VK_FORMAT_UNDEFINED || imageCount == 0) {
67 throw mxvk::Exception("Cannot create 3D sprite before swapchain resources are available");
68 }
69
70 createTexture(surface);
71 createSampler();
72 createQuadBuffers();
73 createDescriptorSetLayout();
74 createCameraBuffers();
75 createDescriptorPool();
76 createDescriptorSets();
77 createPipeline();
78 spriteLoaded = true;
79 std::cout << std::format("mxvk: Created 3D sprite: {}x{}\n", spriteWidth, spriteHeight);
80 }
81
82 void VK_Sprite3D::updateCamera(uint32_t imageIndex, const glm::mat4 &view, const glm::mat4 &proj) {
83 if (imageIndex >= cameraBuffersMapped.size() || cameraBuffersMapped[imageIndex] == nullptr) {
84 return;
85 }
86
87 CameraUBO camera{};
88 camera.view = view;
89 camera.proj = proj;
90 std::memcpy(cameraBuffersMapped[imageIndex], &camera, sizeof(camera));
91 }
92
93 void VK_Sprite3D::drawSprite(const glm::vec3 &position, const glm::vec2 &size, const glm::vec4 &color, float rotationRadians) {
94 if (!spriteLoaded) {
95 throw mxvk::Exception("VK_Sprite3D::drawSprite called before sprite was loaded");
96 }
97 if (size.x <= 0.0f || size.y <= 0.0f) {
98 return;
99 }
100 drawQueue.push_back({position, size, color, rotationRadians});
101 }
102
103 void VK_Sprite3D::render(VkCommandBuffer cmd, uint32_t imageIndex) {
104 if (!spriteLoaded || drawQueue.empty() || imageIndex >= descriptorSets.size()) {
105 return;
106 }
107
108 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
109 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[imageIndex], 0, nullptr);
110
111 VkBuffer vertexBuffers[] = {vertexBuffer};
112 VkDeviceSize offsets[] = {0};
113 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
114 vkCmdBindIndexBuffer(cmd, indexBuffer, 0, VK_INDEX_TYPE_UINT16);
115
116 struct PushConstants {
117 glm::vec4 positionSizeX;
118 glm::vec4 color;
119 glm::vec4 sizeYRotationAlpha;
120 };
121
122 for (const DrawCmd &draw : drawQueue) {
123 PushConstants pc{};
124 pc.positionSizeX = glm::vec4(draw.position, draw.size.x);
125 pc.color = draw.color;
126 pc.sizeYRotationAlpha = glm::vec4(draw.size.y, draw.rotationRadians, alphaDiscardThreshold, 0.0f);
127 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants), &pc);
128 vkCmdDrawIndexed(cmd, 6, 1, 0, 0, 0);
129 }
130 }
131
132 void VK_Sprite3D::clearQueue() { drawQueue.clear(); }
133
135 if (depthTestEnabled == enabled) {
136 return;
137 }
138 depthTestEnabled = enabled;
139 if (spriteLoaded) {
140 createPipeline();
141 }
142 }
143
145 if (depthWriteEnabled == enabled) {
146 return;
147 }
148 depthWriteEnabled = enabled;
149 if (spriteLoaded) {
150 createPipeline();
151 }
152 }
153
155 if (window == nullptr || !spriteLoaded) {
156 return;
157 }
158
159 colorAttachmentFormat = window->getSwapchainFormat();
160 depthAttachmentFormat = window->getDepthFormat();
161 const size_t newImageCount = window->getSwapchainImageCount();
162
163 if (newImageCount != imageCount) {
164 imageCount = newImageCount;
165 destroyDescriptors();
166 destroyCameraBuffers();
167 createDescriptorSetLayout();
168 createCameraBuffers();
169 createDescriptorPool();
170 createDescriptorSets();
171 }
172
173 createPipeline();
174 }
175
177 if (device != VK_NULL_HANDLE) {
178 vkDeviceWaitIdle(device);
179 }
180 drawQueue.clear();
181 destroyPipeline();
182 destroyDescriptors();
183 destroyCameraBuffers();
184 destroyBuffers();
185 destroyTexture();
186
187 device = VK_NULL_HANDLE;
188 physicalDevice = VK_NULL_HANDLE;
189 graphicsQueue = VK_NULL_HANDLE;
190 commandPool = VK_NULL_HANDLE;
191 colorAttachmentFormat = VK_FORMAT_UNDEFINED;
192 depthAttachmentFormat = VK_FORMAT_UNDEFINED;
193 imageCount = 0;
194 spriteLoaded = false;
195 }
196
197 void VK_Sprite3D::createQuadBuffers() {
198 const std::array<Vertex, 4> vertices{{
199 {{-0.5f, -0.5f}, {0.0f, 1.0f}},
200 {{0.5f, -0.5f}, {1.0f, 1.0f}},
201 {{0.5f, 0.5f}, {1.0f, 0.0f}},
202 {{-0.5f, 0.5f}, {0.0f, 0.0f}},
203 }};
204 const std::array<uint16_t, 6> indices{{0, 1, 2, 0, 2, 3}};
205
206 createBuffer(sizeof(vertices), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer, vertexBufferMemory);
207
208 void *data = nullptr;
209 VK_CHECK_RESULT(vkMapMemory(device, vertexBufferMemory, 0, sizeof(vertices), 0, &data));
210 std::memcpy(data, vertices.data(), sizeof(vertices));
211 vkUnmapMemory(device, vertexBufferMemory);
212
213 createBuffer(sizeof(indices), VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, indexBuffer, indexBufferMemory);
214
215 VK_CHECK_RESULT(vkMapMemory(device, indexBufferMemory, 0, sizeof(indices), 0, &data));
216 std::memcpy(data, indices.data(), sizeof(indices));
217 vkUnmapMemory(device, indexBufferMemory);
218 }
219
220 void VK_Sprite3D::createTexture(SDL_Surface *surface) {
221 SDL_Surface *rgbaSurface = convertToRGBA(surface);
222 if (rgbaSurface == nullptr) {
223 throw mxvk::Exception("Failed to convert 3D sprite surface to RGBA");
224 }
225
226 spriteWidth = rgbaSurface->w;
227 spriteHeight = rgbaSurface->h;
228
229 createImage(static_cast<uint32_t>(spriteWidth), static_cast<uint32_t>(spriteHeight), 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, spriteImage, spriteImageMemory);
230
231 const VkDeviceSize imageSize = static_cast<VkDeviceSize>(spriteWidth) * static_cast<VkDeviceSize>(spriteHeight) * 4;
232 VkBuffer stagingBuffer = VK_NULL_HANDLE;
233 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
234 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
235
236 void *data = nullptr;
237 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
238 const int rowBytes = spriteWidth * 4;
239 if (rgbaSurface->pitch == rowBytes) {
240 std::memcpy(data, rgbaSurface->pixels, static_cast<size_t>(imageSize));
241 } else {
242 const auto *src = static_cast<const uint8_t *>(rgbaSurface->pixels);
243 auto *dst = static_cast<uint8_t *>(data);
244 for (int y = 0; y < spriteHeight; ++y) {
245 std::memcpy(dst + y * rowBytes, src + y * rgbaSurface->pitch, static_cast<size_t>(rowBytes));
246 }
247 }
248 vkUnmapMemory(device, stagingMemory);
249
250 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
251 copyBufferToImage(stagingBuffer, spriteImage, static_cast<uint32_t>(spriteWidth), static_cast<uint32_t>(spriteHeight));
252 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
253
254 vkDestroyBuffer(device, stagingBuffer, nullptr);
255 vkFreeMemory(device, stagingMemory, nullptr);
256 SDL_DestroySurface(rgbaSurface);
257
258 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
259 }
260
261 void VK_Sprite3D::createSampler() {
262 VkSamplerCreateInfo samplerInfo{};
263 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
264 samplerInfo.magFilter = VK_FILTER_LINEAR;
265 samplerInfo.minFilter = VK_FILTER_LINEAR;
266 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
267 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
268 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
269 samplerInfo.anisotropyEnable = VK_FALSE;
270 samplerInfo.borderColor = VK_BORDER_COLOR_INT_TRANSPARENT_BLACK;
271 samplerInfo.unnormalizedCoordinates = VK_FALSE;
272 samplerInfo.compareEnable = VK_FALSE;
273 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
274 VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &spriteSampler));
275 }
276
277 void VK_Sprite3D::createDescriptorSetLayout() {
278 if (descriptorSetLayout != VK_NULL_HANDLE) {
279 return;
280 }
281
282 std::array<VkDescriptorSetLayoutBinding, 2> bindings{};
283 bindings[0].binding = 0;
284 bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
285 bindings[0].descriptorCount = 1;
286 bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
287
288 bindings[1].binding = 1;
289 bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
290 bindings[1].descriptorCount = 1;
291 bindings[1].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
292
293 VkDescriptorSetLayoutCreateInfo layoutInfo{};
294 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
295 layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
296 layoutInfo.pBindings = bindings.data();
297 VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout));
298 }
299
300 void VK_Sprite3D::createCameraBuffers() {
301 cameraBuffers.resize(imageCount, VK_NULL_HANDLE);
302 cameraBufferMemory.resize(imageCount, VK_NULL_HANDLE);
303 cameraBuffersMapped.resize(imageCount, nullptr);
304
305 for (size_t i = 0; i < imageCount; ++i) {
306 createBuffer(sizeof(CameraUBO), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, cameraBuffers[i], cameraBufferMemory[i]);
307 VK_CHECK_RESULT(vkMapMemory(device, cameraBufferMemory[i], 0, sizeof(CameraUBO), 0, &cameraBuffersMapped[i]));
308 CameraUBO camera{};
309 std::memcpy(cameraBuffersMapped[i], &camera, sizeof(camera));
310 }
311 }
312
313 void VK_Sprite3D::destroyCameraBuffers() {
314 for (size_t i = 0; i < cameraBuffers.size(); ++i) {
315 if (cameraBuffersMapped[i] != nullptr) {
316 vkUnmapMemory(device, cameraBufferMemory[i]);
317 }
318 if (cameraBuffers[i] != VK_NULL_HANDLE) {
319 vkDestroyBuffer(device, cameraBuffers[i], nullptr);
320 }
321 if (cameraBufferMemory[i] != VK_NULL_HANDLE) {
322 vkFreeMemory(device, cameraBufferMemory[i], nullptr);
323 }
324 }
325 cameraBuffers.clear();
326 cameraBufferMemory.clear();
327 cameraBuffersMapped.clear();
328 }
329
330 void VK_Sprite3D::createDescriptorPool() {
331 std::array<VkDescriptorPoolSize, 2> poolSizes{};
332 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
333 poolSizes[0].descriptorCount = static_cast<uint32_t>(imageCount);
334 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
335 poolSizes[1].descriptorCount = static_cast<uint32_t>(imageCount);
336
337 VkDescriptorPoolCreateInfo poolInfo{};
338 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
339 poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
340 poolInfo.pPoolSizes = poolSizes.data();
341 poolInfo.maxSets = static_cast<uint32_t>(imageCount);
342 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool));
343 }
344
345 void VK_Sprite3D::createDescriptorSets() {
346 descriptorSets.resize(imageCount, VK_NULL_HANDLE);
347 std::vector<VkDescriptorSetLayout> layouts(imageCount, descriptorSetLayout);
348
349 VkDescriptorSetAllocateInfo allocInfo{};
350 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
351 allocInfo.descriptorPool = descriptorPool;
352 allocInfo.descriptorSetCount = static_cast<uint32_t>(imageCount);
353 allocInfo.pSetLayouts = layouts.data();
354 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()));
355
356 for (size_t i = 0; i < imageCount; ++i) {
357 VkDescriptorImageInfo imageInfo{};
358 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
359 imageInfo.imageView = spriteImageView;
360 imageInfo.sampler = spriteSampler;
361
362 VkDescriptorBufferInfo bufferInfo{};
363 bufferInfo.buffer = cameraBuffers[i];
364 bufferInfo.offset = 0;
365 bufferInfo.range = sizeof(CameraUBO);
366
367 std::array<VkWriteDescriptorSet, 2> writes{};
368 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
369 writes[0].dstSet = descriptorSets[i];
370 writes[0].dstBinding = 0;
371 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
372 writes[0].descriptorCount = 1;
373 writes[0].pImageInfo = &imageInfo;
374
375 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
376 writes[1].dstSet = descriptorSets[i];
377 writes[1].dstBinding = 1;
378 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
379 writes[1].descriptorCount = 1;
380 writes[1].pBufferInfo = &bufferInfo;
381
382 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
383 }
384 }
385
386 void VK_Sprite3D::createPipeline() {
387 destroyPipeline();
388
389 auto vertCode = readShaderFile(vertexShaderPath);
390 auto fragCode = readShaderFile(fragmentShaderPath);
391 VkShaderModule vertModule = mxvk::create_shader_module(device, vertCode);
392 VkShaderModule fragModule = mxvk::create_shader_module(device, fragCode);
393
394 VkPipelineShaderStageCreateInfo vertStage{};
395 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
396 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
397 vertStage.module = vertModule;
398 vertStage.pName = "main";
399
400 VkPipelineShaderStageCreateInfo fragStage{};
401 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
402 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
403 fragStage.module = fragModule;
404 fragStage.pName = "main";
405
406 VkPipelineShaderStageCreateInfo stages[] = {vertStage, fragStage};
407
408 VkVertexInputBindingDescription binding{};
409 binding.binding = 0;
410 binding.stride = sizeof(Vertex);
411 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
412
413 std::array<VkVertexInputAttributeDescription, 2> attributes{};
414 attributes[0].binding = 0;
415 attributes[0].location = 0;
416 attributes[0].format = VK_FORMAT_R32G32_SFLOAT;
417 attributes[0].offset = offsetof(Vertex, pos);
418 attributes[1].binding = 0;
419 attributes[1].location = 1;
420 attributes[1].format = VK_FORMAT_R32G32_SFLOAT;
421 attributes[1].offset = offsetof(Vertex, uv);
422
423 VkPipelineVertexInputStateCreateInfo vertexInput{};
424 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
425 vertexInput.vertexBindingDescriptionCount = 1;
426 vertexInput.pVertexBindingDescriptions = &binding;
427 vertexInput.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributes.size());
428 vertexInput.pVertexAttributeDescriptions = attributes.data();
429
430 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
431 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
432 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
433
434 std::array<VkDynamicState, 2> dynamicStates{{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}};
435 VkPipelineDynamicStateCreateInfo dynamicState{};
436 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
437 dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
438 dynamicState.pDynamicStates = dynamicStates.data();
439
440 VkPipelineViewportStateCreateInfo viewportState{};
441 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
442 viewportState.viewportCount = 1;
443 viewportState.scissorCount = 1;
444
445 VkPipelineRasterizationStateCreateInfo rasterizer{};
446 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
447 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
448 rasterizer.cullMode = VK_CULL_MODE_NONE;
449 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
450 rasterizer.lineWidth = 1.0f;
451
452 VkPipelineMultisampleStateCreateInfo multisampling{};
453 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
454 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
455
456 VkPipelineDepthStencilStateCreateInfo depthStencil{};
457 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
458 depthStencil.depthTestEnable = depthTestEnabled ? VK_TRUE : VK_FALSE;
459 depthStencil.depthWriteEnable = depthWriteEnabled ? VK_TRUE : VK_FALSE;
460 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
461
462 VkPipelineColorBlendAttachmentState blendAttachment{};
463 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
464 blendAttachment.blendEnable = VK_TRUE;
465 blendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
466 blendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
467 blendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
468 blendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
469 blendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
470 blendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
471
472 VkPipelineColorBlendStateCreateInfo colorBlending{};
473 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
474 colorBlending.attachmentCount = 1;
475 colorBlending.pAttachments = &blendAttachment;
476
477 VkPushConstantRange pushRange{};
478 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
479 pushRange.offset = 0;
480 pushRange.size = sizeof(glm::vec4) * 3;
481
482 VkPipelineLayoutCreateInfo layoutInfo{};
483 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
484 layoutInfo.setLayoutCount = 1;
485 layoutInfo.pSetLayouts = &descriptorSetLayout;
486 layoutInfo.pushConstantRangeCount = 1;
487 layoutInfo.pPushConstantRanges = &pushRange;
488 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &layoutInfo, nullptr, &pipelineLayout));
489
490 VkPipelineRenderingCreateInfo renderingInfo{};
491 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
492 renderingInfo.colorAttachmentCount = 1;
493 renderingInfo.pColorAttachmentFormats = &colorAttachmentFormat;
494 if (depthAttachmentFormat != VK_FORMAT_UNDEFINED) {
495 renderingInfo.depthAttachmentFormat = depthAttachmentFormat;
496 }
497
498 VkGraphicsPipelineCreateInfo pipelineInfo{};
499 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
500 pipelineInfo.pNext = &renderingInfo;
501 pipelineInfo.stageCount = 2;
502 pipelineInfo.pStages = stages;
503 pipelineInfo.pVertexInputState = &vertexInput;
504 pipelineInfo.pInputAssemblyState = &inputAssembly;
505 pipelineInfo.pViewportState = &viewportState;
506 pipelineInfo.pRasterizationState = &rasterizer;
507 pipelineInfo.pMultisampleState = &multisampling;
508 pipelineInfo.pDepthStencilState = &depthStencil;
509 pipelineInfo.pColorBlendState = &colorBlending;
510 pipelineInfo.pDynamicState = &dynamicState;
511 pipelineInfo.layout = pipelineLayout;
512 pipelineInfo.renderPass = VK_NULL_HANDLE;
513 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo, nullptr, &pipeline));
514
515 vkDestroyShaderModule(device, fragModule, nullptr);
516 vkDestroyShaderModule(device, vertModule, nullptr);
517 }
518
519 void VK_Sprite3D::destroyPipeline() {
520 if (pipeline != VK_NULL_HANDLE) {
521 vkDestroyPipeline(device, pipeline, nullptr);
522 pipeline = VK_NULL_HANDLE;
523 }
524 if (pipelineLayout != VK_NULL_HANDLE) {
525 vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
526 pipelineLayout = VK_NULL_HANDLE;
527 }
528 }
529
530 void VK_Sprite3D::destroyTexture() {
531 if (spriteSampler != VK_NULL_HANDLE) {
532 vkDestroySampler(device, spriteSampler, nullptr);
533 spriteSampler = VK_NULL_HANDLE;
534 }
535 if (spriteImageView != VK_NULL_HANDLE) {
536 vkDestroyImageView(device, spriteImageView, nullptr);
537 spriteImageView = VK_NULL_HANDLE;
538 }
539 if (spriteImage != VK_NULL_HANDLE) {
540 vkDestroyImage(device, spriteImage, nullptr);
541 spriteImage = VK_NULL_HANDLE;
542 }
543 if (spriteImageMemory != VK_NULL_HANDLE) {
544 vkFreeMemory(device, spriteImageMemory, nullptr);
545 spriteImageMemory = VK_NULL_HANDLE;
546 }
547 spriteWidth = 0;
548 spriteHeight = 0;
549 }
550
551 void VK_Sprite3D::destroyBuffers() {
552 if (vertexBuffer != VK_NULL_HANDLE) {
553 vkDestroyBuffer(device, vertexBuffer, nullptr);
554 vertexBuffer = VK_NULL_HANDLE;
555 }
556 if (vertexBufferMemory != VK_NULL_HANDLE) {
557 vkFreeMemory(device, vertexBufferMemory, nullptr);
558 vertexBufferMemory = VK_NULL_HANDLE;
559 }
560 if (indexBuffer != VK_NULL_HANDLE) {
561 vkDestroyBuffer(device, indexBuffer, nullptr);
562 indexBuffer = VK_NULL_HANDLE;
563 }
564 if (indexBufferMemory != VK_NULL_HANDLE) {
565 vkFreeMemory(device, indexBufferMemory, nullptr);
566 indexBufferMemory = VK_NULL_HANDLE;
567 }
568 }
569
570 void VK_Sprite3D::destroyDescriptors() {
571 descriptorSets.clear();
572 if (descriptorPool != VK_NULL_HANDLE) {
573 vkDestroyDescriptorPool(device, descriptorPool, nullptr);
574 descriptorPool = VK_NULL_HANDLE;
575 }
576 if (descriptorSetLayout != VK_NULL_HANDLE) {
577 vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
578 descriptorSetLayout = VK_NULL_HANDLE;
579 }
580 }
581
582 void VK_Sprite3D::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory) const {
583 VkBuffer newBuffer = VK_NULL_HANDLE;
584 VkDeviceMemory newMemory = VK_NULL_HANDLE;
585
586 VkBufferCreateInfo bufferInfo{};
587 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
588 bufferInfo.size = size;
589 bufferInfo.usage = usage;
590 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
591
592 try {
593 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &newBuffer));
594
595 VkMemoryRequirements memRequirements{};
596 vkGetBufferMemoryRequirements(device, newBuffer, &memRequirements);
597
598 VkMemoryAllocateInfo allocInfo{};
599 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
600 allocInfo.allocationSize = memRequirements.size;
601 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
602 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &newMemory));
603 VK_CHECK_RESULT(vkBindBufferMemory(device, newBuffer, newMemory, 0));
604 } catch (...) {
605 if (newBuffer != VK_NULL_HANDLE) {
606 vkDestroyBuffer(device, newBuffer, nullptr);
607 }
608 if (newMemory != VK_NULL_HANDLE) {
609 vkFreeMemory(device, newMemory, nullptr);
610 }
611 throw;
612 }
613
614 if (buffer != VK_NULL_HANDLE) {
615 vkDestroyBuffer(device, buffer, nullptr);
616 }
617 if (bufferMemory != VK_NULL_HANDLE) {
618 vkFreeMemory(device, bufferMemory, nullptr);
619 }
620 buffer = newBuffer;
621 bufferMemory = newMemory;
622 }
623
624 uint32_t VK_Sprite3D::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const {
625 VkPhysicalDeviceMemoryProperties memProperties{};
626 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
627 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
628 if ((typeFilter & (1U << i)) != 0U && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
629 return i;
630 }
631 }
632 throw mxvk::Exception("Failed to find suitable memory type for 3D sprite");
633 }
634
635 VkCommandBuffer VK_Sprite3D::beginSingleTimeCommands() const {
636 VkCommandBufferAllocateInfo allocInfo{};
637 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
638 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
639 allocInfo.commandPool = commandPool;
640 allocInfo.commandBufferCount = 1;
641
642 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
643 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
644
645 VkCommandBufferBeginInfo beginInfo{};
646 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
647 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
648 VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &beginInfo));
649 return commandBuffer;
650 }
651
652 void VK_Sprite3D::endSingleTimeCommands(VkCommandBuffer commandBuffer) const {
653 VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
654
655 VkSubmitInfo submitInfo{};
656 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
657 submitInfo.commandBufferCount = 1;
658 submitInfo.pCommandBuffers = &commandBuffer;
659 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE));
660 VK_CHECK_RESULT(vkQueueWaitIdle(graphicsQueue));
661 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
662 }
663
664 void VK_Sprite3D::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &imageMemory) const {
665 VkImage newImage = VK_NULL_HANDLE;
666 VkDeviceMemory newMemory = VK_NULL_HANDLE;
667
668 VkImageCreateInfo imageInfo{};
669 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
670 imageInfo.imageType = VK_IMAGE_TYPE_2D;
671 imageInfo.extent.width = width;
672 imageInfo.extent.height = height;
673 imageInfo.extent.depth = 1;
674 imageInfo.mipLevels = 1;
675 imageInfo.arrayLayers = 1;
676 imageInfo.format = format;
677 imageInfo.tiling = tiling;
678 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
679 imageInfo.usage = usage;
680 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
681 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
682
683 try {
684 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &newImage));
685
686 VkMemoryRequirements memRequirements{};
687 vkGetImageMemoryRequirements(device, newImage, &memRequirements);
688
689 VkMemoryAllocateInfo allocInfo{};
690 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
691 allocInfo.allocationSize = memRequirements.size;
692 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
693 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &newMemory));
694 VK_CHECK_RESULT(vkBindImageMemory(device, newImage, newMemory, 0));
695 } catch (...) {
696 if (newImage != VK_NULL_HANDLE) {
697 vkDestroyImage(device, newImage, nullptr);
698 }
699 if (newMemory != VK_NULL_HANDLE) {
700 vkFreeMemory(device, newMemory, nullptr);
701 }
702 throw;
703 }
704
705 if (image != VK_NULL_HANDLE) {
706 vkDestroyImage(device, image, nullptr);
707 }
708 if (imageMemory != VK_NULL_HANDLE) {
709 vkFreeMemory(device, imageMemory, nullptr);
710 }
711 image = newImage;
712 imageMemory = newMemory;
713 }
714
715 VkImageView VK_Sprite3D::createImageView(VkImage image, VkFormat format) const {
716 VkImageViewCreateInfo viewInfo{};
717 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
718 viewInfo.image = image;
719 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
720 viewInfo.format = format;
721 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
722 viewInfo.subresourceRange.levelCount = 1;
723 viewInfo.subresourceRange.layerCount = 1;
724
725 VkImageView imageView = VK_NULL_HANDLE;
726 VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &imageView));
727 return imageView;
728 }
729
730 void VK_Sprite3D::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) const {
731 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
732
733 VkImageMemoryBarrier barrier{};
734 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
735 barrier.oldLayout = oldLayout;
736 barrier.newLayout = newLayout;
737 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
738 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
739 barrier.image = image;
740 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
741 barrier.subresourceRange.levelCount = 1;
742 barrier.subresourceRange.layerCount = 1;
743
744 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
745 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
746
747 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
748 barrier.srcAccessMask = 0;
749 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
750 } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
751 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
752 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
753 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
754 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
755 } else {
756 throw mxvk::Exception("Unsupported 3D sprite image layout transition");
757 }
758
759 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
760 endSingleTimeCommands(commandBuffer);
761 }
762
763 void VK_Sprite3D::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) const {
764 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
765
766 VkBufferImageCopy region{};
767 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
768 region.imageSubresource.layerCount = 1;
769 region.imageExtent = {width, height, 1};
770
771 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
772 endSingleTimeCommands(commandBuffer);
773 }
774
775 SDL_Surface *VK_Sprite3D::convertToRGBA(SDL_Surface *surface) const {
776 if (surface == nullptr) {
777 return nullptr;
778 }
779 return SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
780 }
781
782 std::vector<char> VK_Sprite3D::readShaderFile(const std::string &path) const {
783 std::ifstream file(path, std::ios::ate | std::ios::binary);
784 if (!file.is_open()) {
785 throw mxvk::Exception("Failed to open 3D sprite shader: " + path);
786 }
787
788 const std::streamsize fileSize = file.tellg();
789 if (fileSize <= 0 || (fileSize % 4) != 0) {
790 throw mxvk::Exception("Invalid 3D sprite shader size: " + path);
791 }
792
793 std::vector<char> buffer(static_cast<size_t>(fileSize));
794 file.seekg(0);
795 file.read(buffer.data(), fileSize);
796 return buffer;
797 }
798
799} // namespace mxvk
void drawSprite(const glm::vec3 &position, const glm::vec2 &size, const glm::vec4 &color=glm::vec4(1.0f), float rotationRadians=0.0f)
Queue a billboard sprite for rendering.
void cleanup()
Destroy all owned Vulkan resources.
void render(VkCommandBuffer cmd, uint32_t imageIndex)
Record all queued billboard draws into the given command buffer.
void setDepthWriteEnabled(bool enabled)
Enable or disable depth writes for the 3D sprite pipeline.
void setDepthTestEnabled(bool enabled)
Enable or disable depth testing for the 3D sprite pipeline.
void load(VK_Window *window, const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Load sprite texture and build the 3D billboard pipeline from a PNG file.
void clearQueue()
Discard all queued sprite draws without rendering them.
void resize(VK_Window *window)
Rebuild swapchain-dependent resources after resize.
void updateCamera(uint32_t imageIndex, const glm::mat4 &view, const glm::mat4 &proj)
Upload the current camera matrices for one swapchain image.
~VK_Sprite3D()
Destroy owned Vulkan resources.
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:39
VkDevice getDevice() const noexcept
Get the Vulkan logical device handle.
Definition mxvk.hpp:198
VkQueue getGraphicsQueue() const noexcept
Get the graphics queue handle.
Definition mxvk.hpp:204
VkCommandPool getCommandPool() const noexcept
Get the command pool used for graphics/upload work.
Definition mxvk.hpp:207
size_t getSwapchainImageCount() const noexcept
Get the number of swapchain images currently allocated.
Definition mxvk.hpp:239
VkFormat getDepthFormat() const noexcept
Get the depth format used for dynamic rendering attachments.
Definition mxvk.hpp:236
VkPipelineCache getPipelineCache() const noexcept
Get the persistent Vulkan pipeline cache used by framework pipelines.
Definition mxvk.hpp:210
VkPhysicalDevice getPhysicalDevice() const noexcept
Get the Vulkan physical device handle.
Definition mxvk.hpp:201
VkFormat getSwapchainFormat() const noexcept
Get the swapchain color format.
Definition mxvk.hpp:213
PNG image loading and saving utilities via SDL3.
World-space textured billboard renderer for MXVK dynamic rendering.
#define VK_CHECK_RESULT(f)
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
VkShaderModule create_shader_module(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
Definition mxvk_png.cpp:89