libmx2 2.2.1
C++20 multimedia and game-engine library (SDL2 / OpenGL / Vulkan)
Loading...
Searching...
No Matches
vk_sprite.cpp
Go to the documentation of this file.
1/**
2 * @file vk_sprite.cpp
3 * @brief Implementation of mx::VKSprite Vulkan 2-D sprite renderer.
4 */
5#include"vk_sprite.hpp"
6#include"loadpng.hpp"
7
8namespace mx {
9
10 VKSprite::VKSprite(VkDevice dev, VkPhysicalDevice physDev, VkQueue gQueue, VkCommandPool cmdPool)
11 : device(dev), physicalDevice(physDev), graphicsQueue(gQueue), commandPool(cmdPool) {
12 }
13
15 vkDeviceWaitIdle(device);
16 drawQueue.clear();
17
19
20 if (quadVertexBuffer != VK_NULL_HANDLE) {
21 vkDestroyBuffer(device, quadVertexBuffer, nullptr);
22 vkFreeMemory(device, quadVertexBufferMemory, nullptr);
23 }
24 if (quadIndexBuffer != VK_NULL_HANDLE) {
25 vkDestroyBuffer(device, quadIndexBuffer, nullptr);
26 vkFreeMemory(device, quadIndexBufferMemory, nullptr);
27 }
28
29 if (descriptorPool != VK_NULL_HANDLE) {
30 vkDestroyDescriptorPool(device, descriptorPool, nullptr);
31 }
32
33 if (spriteSampler != VK_NULL_HANDLE) {
34 vkDestroySampler(device, spriteSampler, nullptr);
35 }
36
37 if (spriteImageView != VK_NULL_HANDLE) {
38 vkDestroyImageView(device, spriteImageView, nullptr);
39 }
40
41 if (spriteImage != VK_NULL_HANDLE) {
42 vkDestroyImage(device, spriteImage, nullptr);
43 vkFreeMemory(device, spriteImageMemory, nullptr);
44 }
45
46 if (fragmentShaderModule != VK_NULL_HANDLE) {
47 vkDestroyShaderModule(device, fragmentShaderModule, nullptr);
48 }
49
50 if (customPipeline != VK_NULL_HANDLE) {
51 vkDestroyPipeline(device, customPipeline, nullptr);
52 }
53
54 if (customPipelineLayout != VK_NULL_HANDLE) {
55 vkDestroyPipelineLayout(device, customPipelineLayout, nullptr);
56 }
57
60 }
61
64
65 if (descriptorPool != VK_NULL_HANDLE) {
66 vkDestroyDescriptorPool(device, descriptorPool, nullptr);
67 descriptorPool = VK_NULL_HANDLE;
68 descriptorSet = VK_NULL_HANDLE;
69 }
70
71 // Extended descriptor set references spriteImageView/spriteSampler,
72 // so it must be invalidated when those are destroyed.
73 if (extendedDescriptorPool != VK_NULL_HANDLE) {
74 vkDestroyDescriptorPool(device, extendedDescriptorPool, nullptr);
75 extendedDescriptorPool = VK_NULL_HANDLE;
76 extendedDescriptorSet = VK_NULL_HANDLE;
77 }
78
79 if (spriteSampler != VK_NULL_HANDLE) {
80 vkDestroySampler(device, spriteSampler, nullptr);
81 spriteSampler = VK_NULL_HANDLE;
82 }
83
84 if (spriteImageView != VK_NULL_HANDLE) {
85 vkDestroyImageView(device, spriteImageView, nullptr);
86 spriteImageView = VK_NULL_HANDLE;
87 }
88
89 if (spriteImage != VK_NULL_HANDLE) {
90 vkDestroyImage(device, spriteImage, nullptr);
91 spriteImage = VK_NULL_HANDLE;
92 }
93 if (spriteImageMemory != VK_NULL_HANDLE) {
94 vkFreeMemory(device, spriteImageMemory, nullptr);
95 spriteImageMemory = VK_NULL_HANDLE;
96 }
97
98 if (fragmentShaderModule != VK_NULL_HANDLE) {
99 vkDestroyShaderModule(device, fragmentShaderModule, nullptr);
100 fragmentShaderModule = VK_NULL_HANDLE;
101 }
102
103 if (customPipeline != VK_NULL_HANDLE) {
104 vkDestroyPipeline(device, customPipeline, nullptr);
105 customPipeline = VK_NULL_HANDLE;
106 }
107
108 if (customPipelineLayout != VK_NULL_HANDLE) {
109 vkDestroyPipelineLayout(device, customPipelineLayout, nullptr);
110 customPipelineLayout = VK_NULL_HANDLE;
111 }
112
113 hasCustomShader = false;
114 spriteLoaded = false;
115 }
116
124
125 void VKSprite::setMouseState(float mx, float my, float pressed, float reserved) {
126 extendedUBOData.mouse = glm::vec4(mx, my, pressed, reserved);
127 }
128
129 void VKSprite::setUniform0(float x, float y, float z, float w) {
130 extendedUBOData.u0 = glm::vec4(x, y, z, w);
131 }
132
133 void VKSprite::setUniform1(float x, float y, float z, float w) {
134 extendedUBOData.u1 = glm::vec4(x, y, z, w);
135 }
136
137 void VKSprite::setUniform2(float x, float y, float z, float w) {
138 extendedUBOData.u2 = glm::vec4(x, y, z, w);
139 }
140
141 void VKSprite::setUniform3(float x, float y, float z, float w) {
142 extendedUBOData.u3 = glm::vec4(x, y, z, w);
143 }
144
146 if (extendedUBOBuffer != VK_NULL_HANDLE) return;
147 createBuffer(sizeof(SpriteExtendedUBO), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
148 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
151 memset(extendedUBOMapped, 0, sizeof(SpriteExtendedUBO));
152 }
153
158
160 if (extendedDescriptorSetLayout != VK_NULL_HANDLE) return;
161
162 std::array<VkDescriptorSetLayoutBinding, 2> bindings{};
163 // binding 0: combined image sampler (same as original)
164 bindings[0].binding = 0;
165 bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
166 bindings[0].descriptorCount = 1;
167 bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
168 bindings[0].pImmutableSamplers = nullptr;
169 // binding 1: uniform buffer for extended data
170 bindings[1].binding = 1;
171 bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
172 bindings[1].descriptorCount = 1;
173 bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
174 bindings[1].pImmutableSamplers = nullptr;
175
176 VkDescriptorSetLayoutCreateInfo layoutInfo{};
177 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
178 layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
179 layoutInfo.pBindings = bindings.data();
180
181 VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &extendedDescriptorSetLayout));
183 }
184
186 if (extendedDescriptorSetLayout == VK_NULL_HANDLE || spriteImageView == VK_NULL_HANDLE ||
187 spriteSampler == VK_NULL_HANDLE || extendedUBOBuffer == VK_NULL_HANDLE) return;
188
189 if (extendedDescriptorPool != VK_NULL_HANDLE) {
190 vkDestroyDescriptorPool(device, extendedDescriptorPool, nullptr);
191 extendedDescriptorPool = VK_NULL_HANDLE;
192 extendedDescriptorSet = VK_NULL_HANDLE;
193 }
194
195 std::array<VkDescriptorPoolSize, 2> poolSizes{};
196 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
197 poolSizes[0].descriptorCount = 1;
198 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
199 poolSizes[1].descriptorCount = 1;
200
201 VkDescriptorPoolCreateInfo poolInfo{};
202 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
203 poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
204 poolInfo.pPoolSizes = poolSizes.data();
205 poolInfo.maxSets = 1;
206
207 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo, nullptr, &extendedDescriptorPool));
208
209 VkDescriptorSetAllocateInfo allocInfo{};
210 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
211 allocInfo.descriptorPool = extendedDescriptorPool;
212 allocInfo.descriptorSetCount = 1;
213 allocInfo.pSetLayouts = &extendedDescriptorSetLayout;
214
215 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &extendedDescriptorSet));
216
217 VkDescriptorImageInfo imageInfo{};
218 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
219 imageInfo.imageView = spriteImageView;
220 imageInfo.sampler = spriteSampler;
221
222 VkDescriptorBufferInfo bufferInfo{};
223 bufferInfo.buffer = extendedUBOBuffer;
224 bufferInfo.offset = 0;
225 bufferInfo.range = sizeof(SpriteExtendedUBO);
226
227 std::array<VkWriteDescriptorSet, 2> writes{};
228 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
229 writes[0].dstSet = extendedDescriptorSet;
230 writes[0].dstBinding = 0;
231 writes[0].dstArrayElement = 0;
232 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
233 writes[0].descriptorCount = 1;
234 writes[0].pImageInfo = &imageInfo;
235
236 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
237 writes[1].dstSet = extendedDescriptorSet;
238 writes[1].dstBinding = 1;
239 writes[1].dstArrayElement = 0;
240 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
241 writes[1].descriptorCount = 1;
242 writes[1].pBufferInfo = &bufferInfo;
243
244 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
245 }
246
248 if (extendedDescriptorPool != VK_NULL_HANDLE) {
249 vkDestroyDescriptorPool(device, extendedDescriptorPool, nullptr);
250 extendedDescriptorPool = VK_NULL_HANDLE;
251 extendedDescriptorSet = VK_NULL_HANDLE;
252 }
254 vkDestroyDescriptorSetLayout(device, extendedDescriptorSetLayout, nullptr);
255 extendedDescriptorSetLayout = VK_NULL_HANDLE;
257 }
258 if (extendedUBOBuffer != VK_NULL_HANDLE) {
259 if (extendedUBOMapped) {
260 vkUnmapMemory(device, extendedUBOMemory);
261 extendedUBOMapped = nullptr;
262 }
263 vkDestroyBuffer(device, extendedUBOBuffer, nullptr);
264 vkFreeMemory(device, extendedUBOMemory, nullptr);
265 extendedUBOBuffer = VK_NULL_HANDLE;
266 extendedUBOMemory = VK_NULL_HANDLE;
267 }
268 extendedUBOEnabled = false;
269 }
270
271 void VKSprite::createStagingResources(VkDeviceSize size) {
273 return;
274 }
276
277 createBuffer(size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
278 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
280
281 try {
284 VkCommandBufferAllocateInfo allocInfo{};
285 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
286 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
287 allocInfo.commandPool = commandPool;
288 allocInfo.commandBufferCount = 1;
289 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &uploadCmdBuffer));
290 VkFenceCreateInfo fenceInfo{};
291 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
292 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
293 VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &uploadFence));
295 } catch (...) {
296 if (uploadCmdBuffer != VK_NULL_HANDLE) {
297 vkFreeCommandBuffers(device, commandPool, 1, &uploadCmdBuffer);
298 uploadCmdBuffer = VK_NULL_HANDLE;
299 }
301 vkUnmapMemory(device, persistentStagingMemory);
302 persistentStagingMapped = nullptr;
303 }
304 if (persistentStagingBuffer != VK_NULL_HANDLE) {
305 vkDestroyBuffer(device, persistentStagingBuffer, nullptr);
306 persistentStagingBuffer = VK_NULL_HANDLE;
307 }
308 if (persistentStagingMemory != VK_NULL_HANDLE) {
309 vkFreeMemory(device, persistentStagingMemory, nullptr);
310 persistentStagingMemory = VK_NULL_HANDLE;
311 }
313 throw;
314 }
315 }
316
318 if (!stagingResourcesCreated) return;
319
320 if (uploadFence != VK_NULL_HANDLE) {
321 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
322 vkDestroyFence(device, uploadFence, nullptr);
323 uploadFence = VK_NULL_HANDLE;
324 }
325 if (uploadCmdBuffer != VK_NULL_HANDLE) {
326 vkFreeCommandBuffers(device, commandPool, 1, &uploadCmdBuffer);
327 uploadCmdBuffer = VK_NULL_HANDLE;
328 }
329 if (persistentStagingBuffer != VK_NULL_HANDLE) {
330 vkUnmapMemory(device, persistentStagingMemory);
331 vkDestroyBuffer(device, persistentStagingBuffer, nullptr);
332 vkFreeMemory(device, persistentStagingMemory, nullptr);
333 persistentStagingBuffer = VK_NULL_HANDLE;
334 persistentStagingMemory = VK_NULL_HANDLE;
335 persistentStagingMapped = nullptr;
337 }
339 }
340
342 if (instanceBuffer != VK_NULL_HANDLE) {
344 vkUnmapMemory(device, instanceBufferMemory);
345 instanceBufferMapped = nullptr;
346 }
347 vkDestroyBuffer(device, instanceBuffer, nullptr);
348 vkFreeMemory(device, instanceBufferMemory, nullptr);
349 instanceBuffer = VK_NULL_HANDLE;
350 instanceBufferMemory = VK_NULL_HANDLE;
352 }
353 if (instancedPipeline != VK_NULL_HANDLE) {
354 vkDestroyPipeline(device, instancedPipeline, nullptr);
355 instancedPipeline = VK_NULL_HANDLE;
356 }
357 if (instancedPipelineLayout != VK_NULL_HANDLE) {
358 vkDestroyPipelineLayout(device, instancedPipelineLayout, nullptr);
359 instancedPipelineLayout = VK_NULL_HANDLE;
360 }
361 instancingEnabled = false;
362 }
363
364 void VKSprite::ensureInstanceBuffer(uint32_t count) {
365 if (instanceBufferCapacity >= count && instanceBuffer != VK_NULL_HANDLE) return;
366
367 if (instanceBuffer != VK_NULL_HANDLE) {
369 vkUnmapMemory(device, instanceBufferMemory);
370 instanceBufferMapped = nullptr;
371 }
372 vkDestroyBuffer(device, instanceBuffer, nullptr);
373 vkFreeMemory(device, instanceBufferMemory, nullptr);
374 instanceBuffer = VK_NULL_HANDLE;
375 instanceBufferMemory = VK_NULL_HANDLE;
376 }
377
378 VkDeviceSize size = sizeof(SpriteInstanceData) * count;
379 createBuffer(size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
380 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
382
385 }
386
387 void VKSprite::enableInstancing(uint32_t maxInstances,
388 const std::string &instanceVertShaderPath,
389 const std::string &instanceFragShaderPath) {
390 if (renderPass == VK_NULL_HANDLE || descriptorSetLayout == VK_NULL_HANDLE) {
391 throw mx::Exception("VKSprite::enableInstancing called before renderPass/descriptorSetLayout set");
392 }
393 ensureInstanceBuffer(maxInstances);
395 instanceVertPath_ = instanceVertShaderPath;
396 instanceFragPath_ = instanceFragShaderPath;
397 createInstancedPipeline(instanceVertShaderPath, instanceFragShaderPath);
398 instancingEnabled = true;
399 }
400
401 void VKSprite::createInstancedPipeline(const std::string &vertPath, const std::string &fragPath) {
402 if (instancedPipeline != VK_NULL_HANDLE) {
403 vkDestroyPipeline(device, instancedPipeline, nullptr);
404 instancedPipeline = VK_NULL_HANDLE;
405 }
406 if (instancedPipelineLayout != VK_NULL_HANDLE) {
407 vkDestroyPipelineLayout(device, instancedPipelineLayout, nullptr);
408 instancedPipelineLayout = VK_NULL_HANDLE;
409 }
410
411 auto vertShaderCode = readShaderFile(vertPath);
412 auto fragShaderCode = readShaderFile(fragPath);
413 VkShaderModule vertModule = createShaderModule(vertShaderCode);
414 VkShaderModule fragModule = createShaderModule(fragShaderCode);
415
416 VkPipelineShaderStageCreateInfo vertStageInfo{};
417 vertStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
418 vertStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
419 vertStageInfo.module = vertModule;
420 vertStageInfo.pName = "main";
421
422 VkPipelineShaderStageCreateInfo fragStageInfo{};
423 fragStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
424 fragStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
425 fragStageInfo.module = fragModule;
426 fragStageInfo.pName = "main";
427
428 VkPipelineShaderStageCreateInfo shaderStages[] = { vertStageInfo, fragStageInfo };
429
430 std::array<VkVertexInputBindingDescription, 2> bindingDescs{};
431 bindingDescs[0].binding = 0;
432 bindingDescs[0].stride = sizeof(float) * 4;
433 bindingDescs[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
434 bindingDescs[1].binding = 1;
435 bindingDescs[1].stride = sizeof(SpriteInstanceData);
436 bindingDescs[1].inputRate = VK_VERTEX_INPUT_RATE_INSTANCE;
437
438 std::array<VkVertexInputAttributeDescription, 4> attrDescs{};
439
440 attrDescs[0].binding = 0;
441 attrDescs[0].location = 0;
442 attrDescs[0].format = VK_FORMAT_R32G32_SFLOAT;
443 attrDescs[0].offset = 0;
444
445 attrDescs[1].binding = 0;
446 attrDescs[1].location = 1;
447 attrDescs[1].format = VK_FORMAT_R32G32_SFLOAT;
448 attrDescs[1].offset = sizeof(float) * 2;
449
450 attrDescs[2].binding = 1;
451 attrDescs[2].location = 2;
452 attrDescs[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
453 attrDescs[2].offset = 0;
454
455 attrDescs[3].binding = 1;
456 attrDescs[3].location = 3;
457 attrDescs[3].format = VK_FORMAT_R32G32B32A32_SFLOAT;
458 attrDescs[3].offset = sizeof(float) * 4;
459
460 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
461 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
462 vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(bindingDescs.size());
463 vertexInputInfo.pVertexBindingDescriptions = bindingDescs.data();
464 vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attrDescs.size());
465 vertexInputInfo.pVertexAttributeDescriptions = attrDescs.data();
466
467 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
468 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
469 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
470 inputAssembly.primitiveRestartEnable = VK_FALSE;
471
472 std::vector<VkDynamicState> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
473 VkPipelineDynamicStateCreateInfo dynamicState{};
474 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
475 dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
476 dynamicState.pDynamicStates = dynamicStates.data();
477
478 VkPipelineViewportStateCreateInfo viewportState{};
479 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
480 viewportState.viewportCount = 1;
481 viewportState.scissorCount = 1;
482
483 VkPipelineRasterizationStateCreateInfo rasterizer{};
484 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
485 rasterizer.depthClampEnable = VK_FALSE;
486 rasterizer.rasterizerDiscardEnable = VK_FALSE;
487 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
488 rasterizer.lineWidth = 1.0f;
489 rasterizer.cullMode = VK_CULL_MODE_NONE;
490 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
491 rasterizer.depthBiasEnable = VK_FALSE;
492
493 VkPipelineMultisampleStateCreateInfo multisampling{};
494 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
495 multisampling.sampleShadingEnable = VK_FALSE;
496 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
497
498 VkPipelineDepthStencilStateCreateInfo depthStencil{};
499 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
500 depthStencil.depthTestEnable = VK_FALSE;
501 depthStencil.depthWriteEnable = VK_FALSE;
502
503 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
504 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
505 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
506 colorBlendAttachment.blendEnable = VK_TRUE;
507 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
508 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
509 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
510 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
511 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
512 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
513
514 VkPipelineColorBlendStateCreateInfo colorBlending{};
515 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
516 colorBlending.logicOpEnable = VK_FALSE;
517 colorBlending.attachmentCount = 1;
518 colorBlending.pAttachments = &colorBlendAttachment;
519
520 VkPushConstantRange pushConstantRange{};
521 pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
522 pushConstantRange.offset = 0;
523 pushConstantRange.size = sizeof(float) * 2;
524
525 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
526 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
527 pipelineLayoutInfo.setLayoutCount = 1;
528 pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
529 pipelineLayoutInfo.pushConstantRangeCount = 1;
530 pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
531
532 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &instancedPipelineLayout));
533
534 VkGraphicsPipelineCreateInfo pipelineInfo{};
535 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
536 pipelineInfo.stageCount = 2;
537 pipelineInfo.pStages = shaderStages;
538 pipelineInfo.pVertexInputState = &vertexInputInfo;
539 pipelineInfo.pInputAssemblyState = &inputAssembly;
540 pipelineInfo.pViewportState = &viewportState;
541 pipelineInfo.pRasterizationState = &rasterizer;
542 pipelineInfo.pMultisampleState = &multisampling;
543 pipelineInfo.pDepthStencilState = &depthStencil;
544 pipelineInfo.pColorBlendState = &colorBlending;
545 pipelineInfo.pDynamicState = &dynamicState;
546 pipelineInfo.layout = instancedPipelineLayout;
547 pipelineInfo.renderPass = renderPass;
548 pipelineInfo.subpass = 0;
549 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
550
551 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &instancedPipeline));
552
553 vkDestroyShaderModule(device, vertModule, nullptr);
554 vkDestroyShaderModule(device, fragModule, nullptr);
555 }
556
558 if (!hasCustomShader || fragmentShaderModule == VK_NULL_HANDLE) return;
559 if (renderPass == VK_NULL_HANDLE || descriptorSetLayout == VK_NULL_HANDLE) return;
560
561 if (customPipeline != VK_NULL_HANDLE) {
562 vkDestroyPipeline(device, customPipeline, nullptr);
563 customPipeline = VK_NULL_HANDLE;
564 }
565 if (customPipelineLayout != VK_NULL_HANDLE) {
566 vkDestroyPipelineLayout(device, customPipelineLayout, nullptr);
567 customPipelineLayout = VK_NULL_HANDLE;
568 }
569
570 std::string vertPath = vertexShaderPath.empty() ? "data/sprite_vert.spv" : vertexShaderPath;
571 auto vertShaderCode = readShaderFile(vertPath);
572 VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);
573
574 VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
575 vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
576 vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
577 vertShaderStageInfo.module = vertShaderModule;
578 vertShaderStageInfo.pName = "main";
579
580 VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
581 fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
582 fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
583 fragShaderStageInfo.module = fragmentShaderModule;
584 fragShaderStageInfo.pName = "main";
585
586 VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo };
587
588 VkVertexInputBindingDescription bindingDescription{};
589 bindingDescription.binding = 0;
590 bindingDescription.stride = sizeof(float) * 4;
591 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
592
593 std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
594 attributeDescriptions[0].binding = 0;
595 attributeDescriptions[0].location = 0;
596 attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
597 attributeDescriptions[0].offset = 0;
598 attributeDescriptions[1].binding = 0;
599 attributeDescriptions[1].location = 1;
600 attributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;
601 attributeDescriptions[1].offset = sizeof(float) * 2;
602
603 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
604 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
605 vertexInputInfo.vertexBindingDescriptionCount = 1;
606 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
607 vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
608 vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
609
610 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
611 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
612 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
613 inputAssembly.primitiveRestartEnable = VK_FALSE;
614
615 std::vector<VkDynamicState> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
616 VkPipelineDynamicStateCreateInfo dynamicState{};
617 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
618 dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
619 dynamicState.pDynamicStates = dynamicStates.data();
620
621 VkPipelineViewportStateCreateInfo viewportState{};
622 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
623 viewportState.viewportCount = 1;
624 viewportState.scissorCount = 1;
625
626 VkPipelineRasterizationStateCreateInfo rasterizer{};
627 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
628 rasterizer.depthClampEnable = VK_FALSE;
629 rasterizer.rasterizerDiscardEnable = VK_FALSE;
630 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
631 rasterizer.lineWidth = 1.0f;
632 rasterizer.cullMode = VK_CULL_MODE_NONE;
633 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
634 rasterizer.depthBiasEnable = VK_FALSE;
635
636 VkPipelineMultisampleStateCreateInfo multisampling{};
637 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
638 multisampling.sampleShadingEnable = VK_FALSE;
639 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
640
641 VkPipelineDepthStencilStateCreateInfo depthStencil{};
642 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
643 depthStencil.depthTestEnable = VK_FALSE;
644 depthStencil.depthWriteEnable = VK_FALSE;
645
646 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
647 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
648 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
649 colorBlendAttachment.blendEnable = VK_TRUE;
650 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
651 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
652 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
653 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
654 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
655 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
656
657 VkPipelineColorBlendStateCreateInfo colorBlending{};
658 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
659 colorBlending.logicOpEnable = VK_FALSE;
660 colorBlending.attachmentCount = 1;
661 colorBlending.pAttachments = &colorBlendAttachment;
662
663 VkPushConstantRange pushConstantRange{};
664 pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
665 pushConstantRange.offset = 0;
666 pushConstantRange.size = sizeof(float) * 12;
667
668 VkDescriptorSetLayout layoutToUseForPipeline = extendedUBOEnabled ? extendedDescriptorSetLayout : descriptorSetLayout;
669
670 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
671 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
672 pipelineLayoutInfo.setLayoutCount = 1;
673 pipelineLayoutInfo.pSetLayouts = &layoutToUseForPipeline;
674 pipelineLayoutInfo.pushConstantRangeCount = 1;
675 pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
676
677 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &customPipelineLayout));
678
679 VkGraphicsPipelineCreateInfo pipelineInfo{};
680 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
681 pipelineInfo.stageCount = 2;
682 pipelineInfo.pStages = shaderStages;
683 pipelineInfo.pVertexInputState = &vertexInputInfo;
684 pipelineInfo.pInputAssemblyState = &inputAssembly;
685 pipelineInfo.pViewportState = &viewportState;
686 pipelineInfo.pRasterizationState = &rasterizer;
687 pipelineInfo.pMultisampleState = &multisampling;
688 pipelineInfo.pDepthStencilState = &depthStencil;
689 pipelineInfo.pColorBlendState = &colorBlending;
690 pipelineInfo.pDynamicState = &dynamicState;
691 pipelineInfo.layout = customPipelineLayout;
692 pipelineInfo.renderPass = renderPass;
693 pipelineInfo.subpass = 0;
694 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
695
696 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &customPipeline));
697
698 vkDestroyShaderModule(device, vertShaderModule, nullptr);
699 }
700
702 if (!hasCustomShader || fragmentShaderModule == VK_NULL_HANDLE) return;
704 }
705
710
712 if (quadBufferCreated) return;
713
714 SpriteVertex vertices[] = {
715 {{0.0f, 0.0f}, {0.0f, 0.0f}},
716 {{1.0f, 0.0f}, {1.0f, 0.0f}},
717 {{1.0f, 1.0f}, {1.0f, 1.0f}},
718 {{0.0f, 1.0f}, {0.0f, 1.0f}}
719 };
720 uint16_t indices[] = {0, 1, 2, 0, 2, 3};
721
722 VkDeviceSize vertexSize = sizeof(vertices);
723 createBuffer(vertexSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
724 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
726
727 void *data;
728 VK_CHECK_RESULT(vkMapMemory(device, quadVertexBufferMemory, 0, vertexSize, 0, &data));
729 memcpy(data, vertices, vertexSize);
730 vkUnmapMemory(device, quadVertexBufferMemory);
731
732 VkDeviceSize indexSize = sizeof(indices);
733 createBuffer(indexSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
734 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
736
737 VK_CHECK_RESULT(vkMapMemory(device, quadIndexBufferMemory, 0, indexSize, 0, &data));
738 memcpy(data, indices, indexSize);
739 vkUnmapMemory(device, quadIndexBufferMemory);
740
741 quadBufferCreated = true;
742 }
743
744 void VKSprite::loadSprite(const std::string &pngPath, const std::string &fragmentShaderPath) {
745 SDL_Surface* surface = png::LoadPNG(pngPath.c_str());
746 if (!surface) {
747 throw mx::Exception("Failed to load sprite image: " + pngPath);
748 }
749 loadSprite(surface, fragmentShaderPath);
750 SDL_FreeSurface(surface);
751 }
752
753 void VKSprite::loadSprite(SDL_Surface* surface, const std::string &fragmentShaderPath) {
754 if (!surface) {
755 throw mx::Exception("VKSprite::loadSprite called with null surface");
756 }
757 if (spriteLoaded || spriteImage != VK_NULL_HANDLE || fragmentShaderModule != VK_NULL_HANDLE) {
759 }
760 SDL_Surface* rgbaSurface = convertToRGBA(surface);
761 if (!rgbaSurface) {
762 throw mx::Exception("Failed to convert sprite surface to RGBA");
763 }
764 spriteWidth = rgbaSurface->w;
765 spriteHeight = rgbaSurface->h;
766 createSpriteTexture(rgbaSurface);
767 SDL_FreeSurface(rgbaSurface);
770 if (!fragmentShaderPath.empty()) {
771 auto shaderCode = readShaderFile(fragmentShaderPath);
773 hasCustomShader = true;
774
775 if (renderPass != VK_NULL_HANDLE && descriptorSetLayout != VK_NULL_HANDLE) {
777 }
778 }
779 spriteLoaded = true;
780 }
781
782 void VKSprite::createEmptySprite(int width, int height, const std::string &vertexShaderPath, const std::string &fragmentShaderPath) {
783 if (width <= 0 || height <= 0) {
784 throw mx::Exception("VKSprite::createEmptySprite invalid dimensions");
785 }
786 if (spriteLoaded || spriteImage != VK_NULL_HANDLE || fragmentShaderModule != VK_NULL_HANDLE) {
788 }
789 spriteWidth = width;
790 spriteHeight = height;
791
792 if (!vertexShaderPath.empty()) {
794 }
795
796 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
797 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
798 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
799
800 VkBuffer stagingBuffer;
801 VkDeviceMemory stagingMemory;
802 VkDeviceSize imageSize = static_cast<VkDeviceSize>(width) * height * 4;
803
804 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
805 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
806 stagingBuffer, stagingMemory);
807
808 void *data;
809 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
810 memset(data, 0, imageSize);
811 vkUnmapMemory(device, stagingMemory);
812
813 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
814 copyBufferToImage(stagingBuffer, spriteImage, width, height);
815 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
816
817 vkDestroyBuffer(device, stagingBuffer, nullptr);
818 vkFreeMemory(device, stagingMemory, nullptr);
819
820 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
824
825 createStagingResources(imageSize);
826
827 if (!fragmentShaderPath.empty()) {
828 auto shaderCode = readShaderFile(fragmentShaderPath);
830 hasCustomShader = true;
831
832 if (renderPass != VK_NULL_HANDLE && descriptorSetLayout != VK_NULL_HANDLE) {
834 }
835 }
836
837 spriteLoaded = true;
838 }
839
840 void VKSprite::updateTexture(SDL_Surface* surface) {
841 if (!surface) {
842 throw mx::Exception("VKSprite::updateTexture called with null surface");
843 }
844 if (!spriteLoaded) {
845 throw mx::Exception("VKSprite::updateTexture called before sprite was loaded");
846 }
847 SDL_Surface* rgbaSurface = convertToRGBA(surface);
848 if (!rgbaSurface) {
849 throw mx::Exception("Failed to convert surface to RGBA in updateTexture");
850 }
851 if (rgbaSurface->w == spriteWidth && rgbaSurface->h == spriteHeight) {
852 updateSpriteTexture(rgbaSurface->pixels, rgbaSurface->w, rgbaSurface->h);
853 } else {
854 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
855 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
856 }
857 if (spriteImageView != VK_NULL_HANDLE) {
858 vkDestroyImageView(device, spriteImageView, nullptr);
859 spriteImageView = VK_NULL_HANDLE;
860 }
861 if (spriteImage != VK_NULL_HANDLE) {
862 vkDestroyImage(device, spriteImage, nullptr);
863 spriteImage = VK_NULL_HANDLE;
864 }
865 if (spriteImageMemory != VK_NULL_HANDLE) {
866 vkFreeMemory(device, spriteImageMemory, nullptr);
867 spriteImageMemory = VK_NULL_HANDLE;
868 }
869 spriteWidth = rgbaSurface->w;
870 spriteHeight = rgbaSurface->h;
871 createSpriteTexture(rgbaSurface);
872 if (descriptorPool != VK_NULL_HANDLE) {
873 vkDestroyDescriptorPool(device, descriptorPool, nullptr);
874 descriptorPool = VK_NULL_HANDLE;
875 descriptorSet = VK_NULL_HANDLE;
876 }
877 if (extendedDescriptorPool != VK_NULL_HANDLE) {
878 vkDestroyDescriptorPool(device, extendedDescriptorPool, nullptr);
879 extendedDescriptorPool = VK_NULL_HANDLE;
880 extendedDescriptorSet = VK_NULL_HANDLE;
881 }
883 }
884 SDL_FreeSurface(rgbaSurface);
885 }
886
887 void VKSprite::updateTexture(const void* pixels, int width, int height, int pitch) {
888 if (!pixels) {
889 throw mx::Exception("VKSprite::updateTexture called with null pixel data");
890 }
891 if (!spriteLoaded) {
892 throw mx::Exception("VKSprite::updateTexture called before sprite was loaded");
893 }
894 if (width <= 0 || height <= 0) {
895 throw mx::Exception("VKSprite::updateTexture invalid dimensions");
896 }
897 int srcPitch = (pitch > 0) ? pitch : width * 4;
898 if (width == spriteWidth && height == spriteHeight && srcPitch == width * 4) {
899 updateSpriteTexture(pixels, width, height);
900 } else if (width == spriteWidth && height == spriteHeight) {
901 std::vector<uint8_t> packed(width * height * 4);
902 const uint8_t* src = static_cast<const uint8_t*>(pixels);
903 for (int row = 0; row < height; ++row) {
904 memcpy(packed.data() + row * width * 4, src + row * srcPitch, width * 4);
905 }
906 updateSpriteTexture(packed.data(), width, height);
907 } else {
908 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
909 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
910 }
911 if (spriteImageView != VK_NULL_HANDLE) {
912 vkDestroyImageView(device, spriteImageView, nullptr);
913 spriteImageView = VK_NULL_HANDLE;
914 }
915 if (spriteImage != VK_NULL_HANDLE) {
916 vkDestroyImage(device, spriteImage, nullptr);
917 spriteImage = VK_NULL_HANDLE;
918 }
919 if (spriteImageMemory != VK_NULL_HANDLE) {
920 vkFreeMemory(device, spriteImageMemory, nullptr);
921 spriteImageMemory = VK_NULL_HANDLE;
922 }
923 spriteWidth = width;
924 spriteHeight = height;
925 std::vector<uint8_t> packed;
926 const void* texData = pixels;
927 if (srcPitch != width * 4) {
928 packed.resize(width * height * 4);
929 const uint8_t* src = static_cast<const uint8_t*>(pixels);
930 for (int row = 0; row < height; ++row) {
931 memcpy(packed.data() + row * width * 4, src + row * srcPitch, width * 4);
932 }
933 texData = packed.data();
934 }
935 SDL_Surface* tmpSurface = SDL_CreateRGBSurfaceFrom(
936 const_cast<void*>(texData), width, height, 32, width * 4,
937 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
938 if (!tmpSurface) {
939 throw mx::Exception("VKSprite::updateTexture failed to create temp surface");
940 }
941 createSpriteTexture(tmpSurface);
942 SDL_FreeSurface(tmpSurface);
943 if (descriptorPool != VK_NULL_HANDLE) {
944 vkDestroyDescriptorPool(device, descriptorPool, nullptr);
945 descriptorPool = VK_NULL_HANDLE;
946 descriptorSet = VK_NULL_HANDLE;
947 }
948 if (extendedDescriptorPool != VK_NULL_HANDLE) {
949 vkDestroyDescriptorPool(device, extendedDescriptorPool, nullptr);
950 extendedDescriptorPool = VK_NULL_HANDLE;
951 extendedDescriptorSet = VK_NULL_HANDLE;
952 }
954 }
955 }
956
957 void VKSprite::updateSpriteTexture(const void* pixels, uint32_t width, uint32_t height) {
958 VkDeviceSize imageSize = static_cast<VkDeviceSize>(width) * height * 4;
959
960 createStagingResources(imageSize);
961 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
962 VK_CHECK_RESULT(vkResetFences(device, 1, &uploadFence));
963 memcpy(persistentStagingMapped, pixels, imageSize);
964 VK_CHECK_RESULT(vkResetCommandBuffer(uploadCmdBuffer, 0));
965 VkCommandBufferBeginInfo beginInfo{};
966 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
967 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
968 VK_CHECK_RESULT(vkBeginCommandBuffer(uploadCmdBuffer, &beginInfo));
969 VkImageMemoryBarrier barrier{};
970 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
971 barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
972 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
973 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
974 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
975 barrier.image = spriteImage;
976 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
977 barrier.subresourceRange.baseMipLevel = 0;
978 barrier.subresourceRange.levelCount = 1;
979 barrier.subresourceRange.baseArrayLayer = 0;
980 barrier.subresourceRange.layerCount = 1;
981 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
982 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
983 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
984 0, 0, nullptr, 0, nullptr, 1, &barrier);
985
986 VkBufferImageCopy region{};
987 region.bufferOffset = 0;
988 region.bufferRowLength = 0;
989 region.bufferImageHeight = 0;
990 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
991 region.imageSubresource.mipLevel = 0;
992 region.imageSubresource.baseArrayLayer = 0;
993 region.imageSubresource.layerCount = 1;
994 region.imageOffset = {0, 0, 0};
995 region.imageExtent = {width, height, 1};
996 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer, spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
997
998 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
999 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1000 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1001 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1002 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
1003 0, 0, nullptr, 0, nullptr, 1, &barrier);
1004
1005 VK_CHECK_RESULT(vkEndCommandBuffer(uploadCmdBuffer));
1006
1007
1008 VkSubmitInfo submitInfo{};
1009 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1010 submitInfo.commandBufferCount = 1;
1011 submitInfo.pCommandBuffers = &uploadCmdBuffer;
1012 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
1013 }
1014
1015 void VKSprite::createSpriteTexture(SDL_Surface* surface) {
1016 createImage(surface->w, surface->h, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
1017 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1018 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
1019
1020 VkBuffer stagingBuffer;
1021 VkDeviceMemory stagingMemory;
1022 VkDeviceSize imageSize = static_cast<VkDeviceSize>(surface->w) * surface->h * 4;
1023
1024 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
1025 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1026 stagingBuffer, stagingMemory);
1027
1028 void *data;
1029 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
1030 memcpy(data, surface->pixels, imageSize);
1031 vkUnmapMemory(device, stagingMemory);
1032
1033 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1034 copyBufferToImage(stagingBuffer, spriteImage, surface->w, surface->h);
1035 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1036
1037 vkDestroyBuffer(device, stagingBuffer, nullptr);
1038 vkFreeMemory(device, stagingMemory, nullptr);
1039
1040 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
1041 }
1042
1044 if (spriteSampler != VK_NULL_HANDLE) {
1045 vkDestroySampler(device, spriteSampler, nullptr);
1046 spriteSampler = VK_NULL_HANDLE;
1047 }
1048 VkSamplerCreateInfo samplerInfo{};
1049 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1050 samplerInfo.magFilter = VK_FILTER_LINEAR;
1051 samplerInfo.minFilter = VK_FILTER_LINEAR;
1052 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1053 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1054 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1055 samplerInfo.anisotropyEnable = VK_FALSE;
1056 samplerInfo.borderColor = VK_BORDER_COLOR_INT_TRANSPARENT_BLACK;
1057 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1058 samplerInfo.compareEnable = VK_FALSE;
1059 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1060 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
1061 samplerInfo.minLod = 0.0f;
1062 samplerInfo.maxLod = 0.0f;
1063 samplerInfo.mipLodBias = 0.0f;
1064
1065 VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &spriteSampler));
1066 }
1067
1068 void VKSprite::drawSprite(int x, int y) {
1070 }
1071
1072 void VKSprite::drawSprite(int x, int y, float scaleX, float scaleY) {
1073 drawSpriteRect(x, y, static_cast<int>(spriteWidth * scaleX), static_cast<int>(spriteHeight * scaleY));
1074 }
1075
1076 void VKSprite::drawSprite(int x, int y, float scaleX, float scaleY, float rotation) {
1077 drawSpriteRect(x, y, static_cast<int>(spriteWidth * scaleX), static_cast<int>(spriteHeight * scaleY));
1078 }
1079
1080 void VKSprite::drawSpriteRect(int x, int y, int w, int h) {
1081 if (!spriteLoaded) {
1082 throw mx::Exception("VKSprite::drawSpriteRect called before sprite was loaded");
1083 }
1084
1085 drawQueue.push_back({static_cast<float>(x), static_cast<float>(y),
1086 static_cast<float>(w), static_cast<float>(h), shaderParams});
1087 }
1088
1089 void VKSprite::setShaderParams(float p1, float p2, float p3, float p4) {
1090 shaderParams = glm::vec4(p1, p2, p3, p4);
1091 }
1092
1093 void VKSprite::renderSprites(VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout,
1094 uint32_t screenWidth, uint32_t screenHeight) {
1095 if (drawQueue.empty() || !spriteLoaded || !quadBufferCreated) return;
1096 if (descriptorSet == VK_NULL_HANDLE) {
1098 }
1099
1100
1101 if (instancingEnabled && instancedPipeline != VK_NULL_HANDLE && instanceBuffer != VK_NULL_HANDLE) {
1102 uint32_t instanceCount = static_cast<uint32_t>(drawQueue.size());
1103
1104
1105 if (instanceCount > instanceBufferCapacity) {
1106 ensureInstanceBuffer(instanceCount * 2);
1107 }
1108
1109
1111 for (uint32_t i = 0; i < instanceCount; ++i) {
1112 const auto &cmd = drawQueue[i];
1113 dst[i].posX = cmd.x;
1114 dst[i].posY = cmd.y;
1115 dst[i].sizeW = cmd.w;
1116 dst[i].sizeH = cmd.h;
1117 dst[i].params[0] = cmd.params.x;
1118 dst[i].params[1] = cmd.params.y;
1119 dst[i].params[2] = cmd.params.z;
1120 dst[i].params[3] = cmd.params.w;
1121 }
1122
1123 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, instancedPipeline);
1124 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, instancedPipelineLayout,
1125 0, 1, &descriptorSet, 0, nullptr);
1126
1127
1128 VkBuffer buffers[] = {quadVertexBuffer, instanceBuffer};
1129 VkDeviceSize bufOffsets[] = {0, 0};
1130 vkCmdBindVertexBuffers(cmdBuffer, 0, 2, buffers, bufOffsets);
1131 vkCmdBindIndexBuffer(cmdBuffer, quadIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
1132
1133
1134 float screenSize[2] = {(float)screenWidth, (float)screenHeight};
1135 vkCmdPushConstants(cmdBuffer, instancedPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT,
1136 0, sizeof(screenSize), screenSize);
1137
1138
1139 vkCmdDrawIndexed(cmdBuffer, 6, instanceCount, 0, 0, 0);
1140 return;
1141 }
1142
1143
1144 VkPipelineLayout layoutToUse = (customPipeline != VK_NULL_HANDLE) ? customPipelineLayout : pipelineLayout;
1145 if (customPipeline != VK_NULL_HANDLE) {
1146 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, customPipeline);
1147 }
1148
1149 // When extended UBO is enabled, update UBO and bind extended descriptor set
1150 if (extendedUBOEnabled && customPipeline != VK_NULL_HANDLE) {
1152 if (extendedDescriptorSet == VK_NULL_HANDLE) {
1154 }
1155 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layoutToUse,
1156 0, 1, &extendedDescriptorSet, 0, nullptr);
1157 } else {
1158 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layoutToUse,
1159 0, 1, &descriptorSet, 0, nullptr);
1160 }
1161
1162 VkBuffer vertexBuffers[] = {quadVertexBuffer};
1163 VkDeviceSize offsets[] = {0};
1164 vkCmdBindVertexBuffers(cmdBuffer, 0, 1, vertexBuffers, offsets);
1165 vkCmdBindIndexBuffer(cmdBuffer, quadIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
1166
1167 for (const auto &cmd : drawQueue) {
1168 struct SpritePushConstants {
1169 float screenWidth;
1170 float screenHeight;
1171 float spritePosX;
1172 float spritePosY;
1173 float spriteSizeW;
1174 float spriteSizeH;
1175 float effectsOn;
1176 float padding2;
1177 float params[4];
1178 } pc {
1179 (float)screenWidth, (float)screenHeight,
1180 cmd.x, cmd.y, cmd.w, cmd.h,
1181 effectsEnabled ? 1.0f : 0.0f, 0.0f,
1182 {cmd.params.x, cmd.params.y, cmd.params.z, cmd.params.w}
1183 };
1184
1185 vkCmdPushConstants(cmdBuffer, layoutToUse, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
1186 0, sizeof(SpritePushConstants), &pc);
1187
1188 vkCmdDrawIndexed(cmdBuffer, 6, 1, 0, 0, 0);
1189 }
1190 }
1191
1193 drawQueue.clear();
1194 }
1195
1197 VkDescriptorPoolSize poolSize{};
1198 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1199 poolSize.descriptorCount = 100;
1200
1201 VkDescriptorPoolCreateInfo poolInfo{};
1202 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1203 poolInfo.poolSizeCount = 1;
1204 poolInfo.pPoolSizes = &poolSize;
1205 poolInfo.maxSets = 100;
1206 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
1207
1208 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool));
1209 }
1210
1211 VkDescriptorSet VKSprite::createDescriptorSet(VkImageView imageView) {
1212 if (descriptorSetLayout == VK_NULL_HANDLE) {
1213 throw mx::Exception("VKSprite::createDescriptorSet called before setDescriptorSetLayout");
1214 }
1215
1216 if (descriptorPool == VK_NULL_HANDLE) {
1218 }
1219
1220 VkDescriptorSetAllocateInfo allocInfo{};
1221 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1222 allocInfo.descriptorPool = descriptorPool;
1223 allocInfo.descriptorSetCount = 1;
1224 allocInfo.pSetLayouts = &descriptorSetLayout;
1225
1226 VkDescriptorSet descSet;
1227 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descSet));
1228
1229 VkDescriptorImageInfo imageInfo{};
1230 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1231 imageInfo.imageView = imageView;
1232 imageInfo.sampler = spriteSampler;
1233
1234 VkWriteDescriptorSet descriptorWrite{};
1235 descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1236 descriptorWrite.dstSet = descSet;
1237 descriptorWrite.dstBinding = 0;
1238 descriptorWrite.dstArrayElement = 0;
1239 descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1240 descriptorWrite.descriptorCount = 1;
1241 descriptorWrite.pImageInfo = &imageInfo;
1242
1243 vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
1244
1245 return descSet;
1246 }
1247
1248 void VKSprite::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
1249 VkMemoryPropertyFlags properties, VkBuffer& buffer,
1250 VkDeviceMemory& bufferMemory) {
1251 VkBufferCreateInfo bufferInfo{};
1252 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1253 bufferInfo.size = size;
1254 bufferInfo.usage = usage;
1255 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1256
1257 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &buffer));
1258
1259 VkMemoryRequirements memRequirements;
1260 vkGetBufferMemoryRequirements(device, buffer, &memRequirements);
1261
1262 VkMemoryAllocateInfo allocInfo{};
1263 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1264 allocInfo.allocationSize = memRequirements.size;
1265 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1266
1267 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory));
1268 VK_CHECK_RESULT(vkBindBufferMemory(device, buffer, bufferMemory, 0));
1269 }
1270
1271 uint32_t VKSprite::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
1272 VkPhysicalDeviceMemoryProperties memProperties;
1273 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
1274
1275 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
1276 if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
1277 return i;
1278 }
1279 }
1280
1281 throw mx::Exception("Failed to find suitable memory type!");
1282 }
1283
1284 void VKSprite::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) {
1285 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1286
1287 VkImageMemoryBarrier barrier{};
1288 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1289 barrier.oldLayout = oldLayout;
1290 barrier.newLayout = newLayout;
1291 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1292 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1293 barrier.image = image;
1294 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1295 barrier.subresourceRange.baseMipLevel = 0;
1296 barrier.subresourceRange.levelCount = 1;
1297 barrier.subresourceRange.baseArrayLayer = 0;
1298 barrier.subresourceRange.layerCount = 1;
1299
1300 VkPipelineStageFlags sourceStage;
1301 VkPipelineStageFlags destinationStage;
1302
1303 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1304 barrier.srcAccessMask = 0;
1305 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1306 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1307 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1308 } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
1309 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1310 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1311 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1312 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1313 } else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1314 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
1315 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1316 sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1317 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1318 } else {
1319 throw std::invalid_argument("unsupported layout transition!");
1320 }
1321
1322 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
1323 endSingleTimeCommands(commandBuffer);
1324 }
1325
1326 void VKSprite::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) {
1327 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1328
1329 VkBufferImageCopy region{};
1330 region.bufferOffset = 0;
1331 region.bufferRowLength = 0;
1332 region.bufferImageHeight = 0;
1333 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1334 region.imageSubresource.mipLevel = 0;
1335 region.imageSubresource.baseArrayLayer = 0;
1336 region.imageSubresource.layerCount = 1;
1337 region.imageOffset = {0, 0, 0};
1338 region.imageExtent = {width, height, 1};
1339
1340 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
1341
1342 endSingleTimeCommands(commandBuffer);
1343 }
1344
1346 VkCommandBufferAllocateInfo allocInfo{};
1347 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1348 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1349 allocInfo.commandPool = commandPool;
1350 allocInfo.commandBufferCount = 1;
1351
1352 VkCommandBuffer commandBuffer;
1353 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
1354
1355 VkCommandBufferBeginInfo beginInfo{};
1356 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1357 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1358
1359 VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &beginInfo));
1360
1361 return commandBuffer;
1362 }
1363
1364 void VKSprite::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
1365 VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
1366
1367 VkSubmitInfo submitInfo{};
1368 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1369 submitInfo.commandBufferCount = 1;
1370 submitInfo.pCommandBuffers = &commandBuffer;
1371
1372 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE));
1373 VK_CHECK_RESULT(vkQueueWaitIdle(graphicsQueue));
1374
1375 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
1376 }
1377
1378 void VKSprite::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling,
1379 VkImageUsageFlags usage, VkMemoryPropertyFlags properties,
1380 VkImage& image, VkDeviceMemory& imageMemory) {
1381 VkImageCreateInfo imageInfo{};
1382 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1383 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1384 imageInfo.extent.width = width;
1385 imageInfo.extent.height = height;
1386 imageInfo.extent.depth = 1;
1387 imageInfo.mipLevels = 1;
1388 imageInfo.arrayLayers = 1;
1389 imageInfo.format = format;
1390 imageInfo.tiling = tiling;
1391 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1392 imageInfo.usage = usage;
1393 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1394 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1395
1396 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &image));
1397
1398 VkMemoryRequirements memRequirements;
1399 vkGetImageMemoryRequirements(device, image, &memRequirements);
1400
1401 VkMemoryAllocateInfo allocInfo{};
1402 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1403 allocInfo.allocationSize = memRequirements.size;
1404 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1405
1406 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory));
1407 VK_CHECK_RESULT(vkBindImageMemory(device, image, imageMemory, 0));
1408 }
1409
1410 VkImageView VKSprite::createImageView(VkImage image, VkFormat format) {
1411 VkImageViewCreateInfo viewInfo{};
1412 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1413 viewInfo.image = image;
1414 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1415 viewInfo.format = format;
1416 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1417 viewInfo.subresourceRange.baseMipLevel = 0;
1418 viewInfo.subresourceRange.levelCount = 1;
1419 viewInfo.subresourceRange.baseArrayLayer = 0;
1420 viewInfo.subresourceRange.layerCount = 1;
1421
1422 VkImageView imageView;
1423 VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &imageView));
1424 return imageView;
1425 }
1426
1427 SDL_Surface* VKSprite::convertToRGBA(SDL_Surface* surface) {
1428 SDL_Surface* converted = SDL_CreateRGBSurface(0, surface->w, surface->h, 32,
1429 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
1430 if (!converted) return nullptr;
1431 SDL_BlitSurface(surface, nullptr, converted, nullptr);
1432 return converted;
1433 }
1434
1435 std::vector<char> VKSprite::readShaderFile(const std::string& filename) {
1436 std::ifstream file(filename, std::ios::ate | std::ios::binary);
1437 if (!file.is_open()) {
1438 throw mx::Exception("Failed to open shader file: " + filename);
1439 }
1440
1441 size_t fileSize = static_cast<size_t>(file.tellg());
1442 std::vector<char> buffer(fileSize);
1443 file.seekg(0);
1444 file.read(buffer.data(), fileSize);
1445 file.close();
1446
1447 return buffer;
1448 }
1449
1450 VkShaderModule VKSprite::createShaderModule(const std::vector<char>& code) {
1451 VkShaderModuleCreateInfo createInfo{};
1452 createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
1453 createInfo.codeSize = code.size();
1454 createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
1455
1456 VkShaderModule shaderModule;
1457 VK_CHECK_RESULT(vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule));
1458 return shaderModule;
1459 }
1460
1461}
General-purpose exception class for the mx library.
Definition exception.hpp:20
bool ownExtendedDescriptorSetLayout
VkDeviceMemory extendedUBOMemory
void createInstancedPipeline(const std::string &vertPath, const std::string &fragPath)
glm::vec4 shaderParams
void setUniform3(float x, float y, float z, float w)
Upload user uniform 3 to the extended UBO.
std::vector< char > readShaderFile(const std::string &filename)
void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
bool hasCustomShader
void * extendedUBOMapped
void destroyInstanceResources()
VkImage spriteImage
void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
VKSprite(VkDevice device, VkPhysicalDevice physicalDevice, VkQueue graphicsQueue, VkCommandPool commandPool)
Construct and record Vulkan context handles.
Definition vk_sprite.cpp:10
VkCommandBuffer uploadCmdBuffer
SDL_Surface * convertToRGBA(SDL_Surface *surface)
void enableExtendedUBO()
Allocate and initialise the extended uniform buffer object.
VkPipelineLayout instancedPipelineLayout
bool instancingEnabled
std::string vertexShaderPath
VkCommandPool commandPool
VkDescriptorSet createDescriptorSet(VkImageView imageView)
void drawSprite(int x, int y)
Queue a draw at the given pixel position.
void rebuildPipeline()
Destroy and recreate the custom graphics pipeline.
void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &imageMemory)
VkDescriptorPool descriptorPool
VkDescriptorSetLayout extendedDescriptorSetLayout
~VKSprite()
Destructor — frees all Vulkan resources.
Definition vk_sprite.cpp:14
bool quadBufferCreated
void drawSpriteRect(int x, int y, int w, int h)
Queue a draw into an explicit destination rectangle.
void destroySpriteResources()
Definition vk_sprite.cpp:62
void updateTexture(SDL_Surface *surface)
Replace the sprite texture from an SDL_Surface.
void renderSprites(VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout, uint32_t screenWidth, uint32_t screenHeight)
Record all queued draw commands into the given command buffer.
std::string instanceFragPath_
VkShaderModule fragmentShaderModule
VkBuffer instanceBuffer
void loadSprite(const std::string &pngPath, const std::string &fragmentShaderPath="")
Load sprite texture from a PNG file.
void ensureInstanceBuffer(uint32_t count)
void * instanceBufferMapped
void createEmptySprite(int width, int height, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a blank (un-initialised) sprite texture.
void createCustomPipeline()
VkBuffer quadVertexBuffer
void setMouseState(float mx, float my, float pressed, float reserved=0.0f)
Upload mouse state to the extended UBO.
void setUniform2(float x, float y, float z, float w)
Upload user uniform 2 to the extended UBO.
void createExtendedDescriptorSetLayout()
bool extendedUBOEnabled
void setUniform1(float x, float y, float z, float w)
Upload user uniform 1 to the extended UBO.
VkPipeline customPipeline
void * persistentStagingMapped
void destroyStagingResources()
void updateExtendedUBO()
VkDevice device
VkDeviceMemory persistentStagingMemory
VkDescriptorPool extendedDescriptorPool
VkRenderPass renderPass
void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
void clearQueue()
Discard all pending draw commands without rendering.
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
void createDescriptorPool()
void createQuadBuffer()
bool stagingResourcesCreated
VkImageView createImageView(VkImage image, VkFormat format)
void setUniform0(float x, float y, float z, float w)
Upload user uniform 0 to the extended UBO.
void setVertexShaderPath(const std::string &path)
Override the vertex shader path (used when rebuilding the pipeline).
VkFence uploadFence
VkDescriptorSet descriptorSet
VkPhysicalDevice physicalDevice
VkDeviceSize persistentStagingSize
VkDeviceMemory instanceBufferMemory
void createSpriteTexture(SDL_Surface *surface)
bool effectsEnabled
uint32_t instanceBufferCapacity
VkBuffer quadIndexBuffer
void endSingleTimeCommands(VkCommandBuffer commandBuffer)
void enableInstancing(uint32_t maxInstances, const std::string &instanceVertShaderPath, const std::string &instanceFragShaderPath)
Enable GPU instancing for this sprite type.
std::string instanceVertPath_
SpriteExtendedUBO extendedUBOData
void createExtendedUBO()
VkBuffer persistentStagingBuffer
VkSampler spriteSampler
Texture sampler.
void destroyExtendedUBO()
VkPipeline instancedPipeline
VkDescriptorSet extendedDescriptorSet
VkBuffer extendedUBOBuffer
void setShaderParams(float p1=0.0f, float p2=0.0f, float p3=0.0f, float p4=0.0f)
Set up to four custom shader float parameters.
VkDeviceMemory quadIndexBufferMemory
VkImageView spriteImageView
void createExtendedDescriptorSet()
void createStagingResources(VkDeviceSize size)
std::vector< SpriteDrawCmd > drawQueue
VkCommandBuffer beginSingleTimeCommands()
void createSampler()
void rebuildInstancedPipeline()
Destroy and recreate the instanced graphics pipeline.
VkShaderModule createShaderModule(const std::vector< char > &code)
void updateSpriteTexture(const void *pixels, uint32_t width, uint32_t height)
VkQueue graphicsQueue
VkPipelineLayout customPipelineLayout
VkDeviceMemory quadVertexBufferMemory
VkDescriptorSetLayout descriptorSetLayout
VkDeviceMemory spriteImageMemory
PNG image loading and saving utilities via SDL.
Definition cfg.cpp:11
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
Definition loadpng.cpp:22
#define VK_CHECK_RESULT(f)
Definition vk.hpp:47
Vulkan 2-D sprite renderer with optional custom shaders and instancing.