18#ifndef VK_CHECK_RESULT
19#define VK_CHECK_RESULT(f) \
22 if (res != VK_SUCCESS) { \
23 throw mxvk::Exception(std::format("Fatal : VkResult is \"{}\" in {} at line {}", static_cast<int>(res), __FILE__, __LINE__)); \
35 const std::string &pngPath,
36 const std::string &vertexPath,
37 const std::string &fragmentPath) {
44 std::cout << std::format(
"mxvk: Loaded 3D sprite PNG: {}\n", pngPath);
49 const std::string &vertexPath,
50 const std::string &fragmentPath) {
51 if (window ==
nullptr) {
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;
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");
74 if (colorAttachmentFormat == VK_FORMAT_UNDEFINED || imageCount == 0) {
75 throw mxvk::Exception(
"Cannot create 3D sprite before swapchain resources are available");
81 createDescriptorSetLayout();
82 createCameraBuffers();
83 createDescriptorPool();
84 createDescriptorSets();
87 std::cout << std::format(
"mxvk: Created 3D sprite: {}x{}\n", spriteWidth, spriteHeight);
91 if (imageIndex >= cameraBuffersMapped.size() || cameraBuffersMapped[imageIndex] ==
nullptr) {
98 std::memcpy(cameraBuffersMapped[imageIndex], &camera,
sizeof(camera));
102 const glm::vec2 &size,
103 const glm::vec4 &color,
104 float rotationRadians) {
106 throw mxvk::Exception(
"VK_Sprite3D::drawSprite called before sprite was loaded");
108 if (size.x <= 0.0f || size.y <= 0.0f) {
111 drawQueue.push_back({position, size, color, rotationRadians});
115 if (!spriteLoaded || drawQueue.empty() || imageIndex >= descriptorSets.size()) {
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);
123 VkBuffer vertexBuffers[] = {vertexBuffer};
124 VkDeviceSize offsets[] = {0};
125 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
126 vkCmdBindIndexBuffer(cmd, indexBuffer, 0, VK_INDEX_TYPE_UINT16);
128 struct PushConstants {
129 glm::vec4 positionSizeX;
131 glm::vec4 sizeYRotationAlpha;
134 for (
const DrawCmd &draw : drawQueue) {
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);
150 if (depthTestEnabled == enabled) {
153 depthTestEnabled = enabled;
160 if (depthWriteEnabled == enabled) {
163 depthWriteEnabled = enabled;
170 if (window ==
nullptr || !spriteLoaded) {
178 if (newImageCount != imageCount) {
179 imageCount = newImageCount;
180 destroyDescriptors();
181 destroyCameraBuffers();
182 createDescriptorSetLayout();
183 createCameraBuffers();
184 createDescriptorPool();
185 createDescriptorSets();
192 if (device != VK_NULL_HANDLE) {
193 vkDeviceWaitIdle(device);
197 destroyDescriptors();
198 destroyCameraBuffers();
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;
209 spriteLoaded =
false;
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}},
219 const std::array<uint16_t, 6> indices{{0, 1, 2, 0, 2, 3}};
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);
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);
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);
234 VK_CHECK_RESULT(vkMapMemory(device, indexBufferMemory, 0,
sizeof(indices), 0, &data));
235 std::memcpy(data, indices.data(),
sizeof(indices));
236 vkUnmapMemory(device, indexBufferMemory);
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");
245 spriteWidth = rgbaSurface->w;
246 spriteHeight = rgbaSurface->h;
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);
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);
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));
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));
272 vkUnmapMemory(device, stagingMemory);
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);
278 vkDestroyBuffer(device, stagingBuffer,
nullptr);
279 vkFreeMemory(device, stagingMemory,
nullptr);
280 SDL_DestroySurface(rgbaSurface);
282 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
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));
301 void VK_Sprite3D::createDescriptorSetLayout() {
302 if (descriptorSetLayout != VK_NULL_HANDLE) {
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;
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;
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));
324 void VK_Sprite3D::createCameraBuffers() {
325 cameraBuffers.resize(imageCount, VK_NULL_HANDLE);
326 cameraBufferMemory.resize(imageCount, VK_NULL_HANDLE);
327 cameraBuffersMapped.resize(imageCount,
nullptr);
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]));
335 std::memcpy(cameraBuffersMapped[i], &camera,
sizeof(camera));
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]);
344 if (cameraBuffers[i] != VK_NULL_HANDLE) {
345 vkDestroyBuffer(device, cameraBuffers[i],
nullptr);
347 if (cameraBufferMemory[i] != VK_NULL_HANDLE) {
348 vkFreeMemory(device, cameraBufferMemory[i],
nullptr);
351 cameraBuffers.clear();
352 cameraBufferMemory.clear();
353 cameraBuffersMapped.clear();
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);
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));
371 void VK_Sprite3D::createDescriptorSets() {
372 descriptorSets.resize(imageCount, VK_NULL_HANDLE);
373 std::vector<VkDescriptorSetLayout> layouts(imageCount, descriptorSetLayout);
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()));
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;
388 VkDescriptorBufferInfo bufferInfo{};
389 bufferInfo.buffer = cameraBuffers[i];
390 bufferInfo.offset = 0;
391 bufferInfo.range =
sizeof(CameraUBO);
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;
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;
408 vkUpdateDescriptorSets(device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
412 void VK_Sprite3D::createPipeline() {
415 auto vertCode = readShaderFile(vertexShaderPath);
416 auto fragCode = readShaderFile(fragmentShaderPath);
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";
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";
432 VkPipelineShaderStageCreateInfo stages[] = {vertStage, fragStage};
434 VkVertexInputBindingDescription binding{};
436 binding.stride =
sizeof(Vertex);
437 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
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);
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();
456 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
457 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
458 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
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();
466 VkPipelineViewportStateCreateInfo viewportState{};
467 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
468 viewportState.viewportCount = 1;
469 viewportState.scissorCount = 1;
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;
478 VkPipelineMultisampleStateCreateInfo multisampling{};
479 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
480 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
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;
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;
499 VkPipelineColorBlendStateCreateInfo colorBlending{};
500 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
501 colorBlending.attachmentCount = 1;
502 colorBlending.pAttachments = &blendAttachment;
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;
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));
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;
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));
542 vkDestroyShaderModule(device, fragModule,
nullptr);
543 vkDestroyShaderModule(device, vertModule,
nullptr);
546 void VK_Sprite3D::destroyPipeline() {
547 if (pipeline != VK_NULL_HANDLE) {
548 vkDestroyPipeline(device, pipeline,
nullptr);
549 pipeline = VK_NULL_HANDLE;
551 if (pipelineLayout != VK_NULL_HANDLE) {
552 vkDestroyPipelineLayout(device, pipelineLayout,
nullptr);
553 pipelineLayout = VK_NULL_HANDLE;
557 void VK_Sprite3D::destroyTexture() {
558 if (spriteSampler != VK_NULL_HANDLE) {
559 vkDestroySampler(device, spriteSampler,
nullptr);
560 spriteSampler = VK_NULL_HANDLE;
562 if (spriteImageView != VK_NULL_HANDLE) {
563 vkDestroyImageView(device, spriteImageView,
nullptr);
564 spriteImageView = VK_NULL_HANDLE;
566 if (spriteImage != VK_NULL_HANDLE) {
567 vkDestroyImage(device, spriteImage,
nullptr);
568 spriteImage = VK_NULL_HANDLE;
570 if (spriteImageMemory != VK_NULL_HANDLE) {
571 vkFreeMemory(device, spriteImageMemory,
nullptr);
572 spriteImageMemory = VK_NULL_HANDLE;
578 void VK_Sprite3D::destroyBuffers() {
579 if (vertexBuffer != VK_NULL_HANDLE) {
580 vkDestroyBuffer(device, vertexBuffer,
nullptr);
581 vertexBuffer = VK_NULL_HANDLE;
583 if (vertexBufferMemory != VK_NULL_HANDLE) {
584 vkFreeMemory(device, vertexBufferMemory,
nullptr);
585 vertexBufferMemory = VK_NULL_HANDLE;
587 if (indexBuffer != VK_NULL_HANDLE) {
588 vkDestroyBuffer(device, indexBuffer,
nullptr);
589 indexBuffer = VK_NULL_HANDLE;
591 if (indexBufferMemory != VK_NULL_HANDLE) {
592 vkFreeMemory(device, indexBufferMemory,
nullptr);
593 indexBufferMemory = VK_NULL_HANDLE;
597 void VK_Sprite3D::destroyDescriptors() {
598 descriptorSets.clear();
599 if (descriptorPool != VK_NULL_HANDLE) {
600 vkDestroyDescriptorPool(device, descriptorPool,
nullptr);
601 descriptorPool = VK_NULL_HANDLE;
603 if (descriptorSetLayout != VK_NULL_HANDLE) {
604 vkDestroyDescriptorSetLayout(device, descriptorSetLayout,
nullptr);
605 descriptorSetLayout = VK_NULL_HANDLE;
609 void VK_Sprite3D::createBuffer(VkDeviceSize size,
610 VkBufferUsageFlags usage,
611 VkMemoryPropertyFlags properties,
613 VkDeviceMemory &bufferMemory)
const {
614 VkBuffer newBuffer = VK_NULL_HANDLE;
615 VkDeviceMemory newMemory = VK_NULL_HANDLE;
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;
624 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo,
nullptr, &newBuffer));
626 VkMemoryRequirements memRequirements{};
627 vkGetBufferMemoryRequirements(device, newBuffer, &memRequirements);
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));
636 if (newBuffer != VK_NULL_HANDLE) {
637 vkDestroyBuffer(device, newBuffer,
nullptr);
639 if (newMemory != VK_NULL_HANDLE) {
640 vkFreeMemory(device, newMemory,
nullptr);
645 if (buffer != VK_NULL_HANDLE) {
646 vkDestroyBuffer(device, buffer,
nullptr);
648 if (bufferMemory != VK_NULL_HANDLE) {
649 vkFreeMemory(device, bufferMemory,
nullptr);
652 bufferMemory = newMemory;
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) {
664 throw mxvk::Exception(
"Failed to find suitable memory type for 3D sprite");
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;
674 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
675 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
677 VkCommandBufferBeginInfo beginInfo{};
678 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
679 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
681 return commandBuffer;
684 void VK_Sprite3D::endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
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));
693 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
696 void VK_Sprite3D::createImage(uint32_t width,
699 VkImageTiling tiling,
700 VkImageUsageFlags usage,
701 VkMemoryPropertyFlags properties,
703 VkDeviceMemory &imageMemory)
const {
704 VkImage newImage = VK_NULL_HANDLE;
705 VkDeviceMemory newMemory = VK_NULL_HANDLE;
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;
723 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo,
nullptr, &newImage));
725 VkMemoryRequirements memRequirements{};
726 vkGetImageMemoryRequirements(device, newImage, &memRequirements);
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));
735 if (newImage != VK_NULL_HANDLE) {
736 vkDestroyImage(device, newImage,
nullptr);
738 if (newMemory != VK_NULL_HANDLE) {
739 vkFreeMemory(device, newMemory,
nullptr);
744 if (image != VK_NULL_HANDLE) {
745 vkDestroyImage(device, image,
nullptr);
747 if (imageMemory != VK_NULL_HANDLE) {
748 vkFreeMemory(device, imageMemory,
nullptr);
751 imageMemory = newMemory;
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;
764 VkImageView imageView = VK_NULL_HANDLE;
765 VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo,
nullptr, &imageView));
769 void VK_Sprite3D::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
770 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
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;
783 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
784 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
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;
795 throw mxvk::Exception(
"Unsupported 3D sprite image layout transition");
798 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0,
799 0,
nullptr, 0,
nullptr, 1, &barrier);
800 endSingleTimeCommands(commandBuffer);
803 void VK_Sprite3D::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
804 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
806 VkBufferImageCopy region{};
807 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
808 region.imageSubresource.layerCount = 1;
809 region.imageExtent = {width, height, 1};
811 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
812 endSingleTimeCommands(commandBuffer);
815 SDL_Surface *VK_Sprite3D::convertToRGBA(SDL_Surface *surface)
const {
816 if (surface ==
nullptr) {
819 return SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
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);
828 const std::streamsize fileSize = file.tellg();
829 if (fileSize <= 0 || (fileSize % 4) != 0) {
830 throw mxvk::Exception(
"Invalid 3D sprite shader size: " + path);
833 std::vector<char> buffer(
static_cast<size_t>(fileSize));
835 file.read(buffer.data(), fileSize);
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.
VkDevice getDevice() const noexcept
Get the Vulkan logical device handle.
VkQueue getGraphicsQueue() const noexcept
Get the graphics queue handle.
VkCommandPool getCommandPool() const noexcept
Get the command pool used for graphics/upload work.
size_t getSwapchainImageCount() const noexcept
Get the number of swapchain images currently allocated.
VkFormat getDepthFormat() const noexcept
Get the depth format used for dynamic rendering attachments.
VkPipelineCache getPipelineCache() const noexcept
Get the persistent Vulkan pipeline cache used by framework pipelines.
VkPhysicalDevice getPhysicalDevice() const noexcept
Get the Vulkan physical device handle.
VkFormat getSwapchainFormat() const noexcept
Get the swapchain color format.
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.
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.