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