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__)); \
39 std::cout << std::format(
"mxvk: Loaded 3D sprite PNG: {}\n", pngPath);
43 if (window ==
nullptr) {
60 vertexShaderPath = vertexPath.empty() ? (std::filesystem::path(MXVK_SPRITE3D_SHADER_DIR) /
"sprite3d.vert.spv").
string() : vertexPath;
61 fragmentShaderPath = fragmentPath.empty() ? (std::filesystem::path(MXVK_SPRITE3D_SHADER_DIR) /
"sprite3d.frag.spv").
string() : fragmentPath;
63 if (device == VK_NULL_HANDLE || physicalDevice == VK_NULL_HANDLE || graphicsQueue == VK_NULL_HANDLE || commandPool == VK_NULL_HANDLE) {
64 throw mxvk::Exception(
"Cannot create 3D sprite before Vulkan render resources are available");
66 if (colorAttachmentFormat == VK_FORMAT_UNDEFINED || imageCount == 0) {
67 throw mxvk::Exception(
"Cannot create 3D sprite before swapchain resources are available");
73 createDescriptorSetLayout();
74 createCameraBuffers();
75 createDescriptorPool();
76 createDescriptorSets();
79 std::cout << std::format(
"mxvk: Created 3D sprite: {}x{}\n", spriteWidth, spriteHeight);
83 if (imageIndex >= cameraBuffersMapped.size() || cameraBuffersMapped[imageIndex] ==
nullptr) {
90 std::memcpy(cameraBuffersMapped[imageIndex], &camera,
sizeof(camera));
93 void VK_Sprite3D::drawSprite(
const glm::vec3 &position,
const glm::vec2 &size,
const glm::vec4 &color,
float rotationRadians) {
95 throw mxvk::Exception(
"VK_Sprite3D::drawSprite called before sprite was loaded");
97 if (size.x <= 0.0f || size.y <= 0.0f) {
100 drawQueue.push_back({position, size, color, rotationRadians});
104 if (!spriteLoaded || drawQueue.empty() || imageIndex >= descriptorSets.size()) {
108 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
109 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[imageIndex], 0,
nullptr);
111 VkBuffer vertexBuffers[] = {vertexBuffer};
112 VkDeviceSize offsets[] = {0};
113 vkCmdBindVertexBuffers(cmd, 0, 1, vertexBuffers, offsets);
114 vkCmdBindIndexBuffer(cmd, indexBuffer, 0, VK_INDEX_TYPE_UINT16);
116 struct PushConstants {
117 glm::vec4 positionSizeX;
119 glm::vec4 sizeYRotationAlpha;
122 for (
const DrawCmd &draw : drawQueue) {
124 pc.positionSizeX = glm::vec4(draw.position, draw.size.x);
125 pc.color = draw.color;
126 pc.sizeYRotationAlpha = glm::vec4(draw.size.y, draw.rotationRadians, alphaDiscardThreshold, 0.0f);
127 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(PushConstants), &pc);
128 vkCmdDrawIndexed(cmd, 6, 1, 0, 0, 0);
135 if (depthTestEnabled == enabled) {
138 depthTestEnabled = enabled;
145 if (depthWriteEnabled == enabled) {
148 depthWriteEnabled = enabled;
155 if (window ==
nullptr || !spriteLoaded) {
163 if (newImageCount != imageCount) {
164 imageCount = newImageCount;
165 destroyDescriptors();
166 destroyCameraBuffers();
167 createDescriptorSetLayout();
168 createCameraBuffers();
169 createDescriptorPool();
170 createDescriptorSets();
177 if (device != VK_NULL_HANDLE) {
178 vkDeviceWaitIdle(device);
182 destroyDescriptors();
183 destroyCameraBuffers();
187 device = VK_NULL_HANDLE;
188 physicalDevice = VK_NULL_HANDLE;
189 graphicsQueue = VK_NULL_HANDLE;
190 commandPool = VK_NULL_HANDLE;
191 colorAttachmentFormat = VK_FORMAT_UNDEFINED;
192 depthAttachmentFormat = VK_FORMAT_UNDEFINED;
194 spriteLoaded =
false;
197 void VK_Sprite3D::createQuadBuffers() {
198 const std::array<Vertex, 4> vertices{{
199 {{-0.5f, -0.5f}, {0.0f, 1.0f}},
200 {{0.5f, -0.5f}, {1.0f, 1.0f}},
201 {{0.5f, 0.5f}, {1.0f, 0.0f}},
202 {{-0.5f, 0.5f}, {0.0f, 0.0f}},
204 const std::array<uint16_t, 6> indices{{0, 1, 2, 0, 2, 3}};
206 createBuffer(
sizeof(vertices), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer, vertexBufferMemory);
208 void *data =
nullptr;
209 VK_CHECK_RESULT(vkMapMemory(device, vertexBufferMemory, 0,
sizeof(vertices), 0, &data));
210 std::memcpy(data, vertices.data(),
sizeof(vertices));
211 vkUnmapMemory(device, vertexBufferMemory);
213 createBuffer(
sizeof(indices), VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, indexBuffer, indexBufferMemory);
215 VK_CHECK_RESULT(vkMapMemory(device, indexBufferMemory, 0,
sizeof(indices), 0, &data));
216 std::memcpy(data, indices.data(),
sizeof(indices));
217 vkUnmapMemory(device, indexBufferMemory);
220 void VK_Sprite3D::createTexture(SDL_Surface *surface) {
221 SDL_Surface *rgbaSurface = convertToRGBA(surface);
222 if (rgbaSurface ==
nullptr) {
223 throw mxvk::Exception(
"Failed to convert 3D sprite surface to RGBA");
226 spriteWidth = rgbaSurface->w;
227 spriteHeight = rgbaSurface->h;
229 createImage(
static_cast<uint32_t
>(spriteWidth),
static_cast<uint32_t
>(spriteHeight), VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
231 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(spriteWidth) *
static_cast<VkDeviceSize
>(spriteHeight) * 4;
232 VkBuffer stagingBuffer = VK_NULL_HANDLE;
233 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
234 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
236 void *data =
nullptr;
237 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
238 const int rowBytes = spriteWidth * 4;
239 if (rgbaSurface->pitch == rowBytes) {
240 std::memcpy(data, rgbaSurface->pixels,
static_cast<size_t>(imageSize));
242 const auto *src =
static_cast<const uint8_t *
>(rgbaSurface->pixels);
243 auto *dst =
static_cast<uint8_t *
>(data);
244 for (
int y = 0; y < spriteHeight; ++y) {
245 std::memcpy(dst + y * rowBytes, src + y * rgbaSurface->pitch,
static_cast<size_t>(rowBytes));
248 vkUnmapMemory(device, stagingMemory);
250 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
251 copyBufferToImage(stagingBuffer, spriteImage,
static_cast<uint32_t
>(spriteWidth),
static_cast<uint32_t
>(spriteHeight));
252 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
254 vkDestroyBuffer(device, stagingBuffer,
nullptr);
255 vkFreeMemory(device, stagingMemory,
nullptr);
256 SDL_DestroySurface(rgbaSurface);
258 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
261 void VK_Sprite3D::createSampler() {
262 VkSamplerCreateInfo samplerInfo{};
263 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
264 samplerInfo.magFilter = VK_FILTER_LINEAR;
265 samplerInfo.minFilter = VK_FILTER_LINEAR;
266 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
267 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
268 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
269 samplerInfo.anisotropyEnable = VK_FALSE;
270 samplerInfo.borderColor = VK_BORDER_COLOR_INT_TRANSPARENT_BLACK;
271 samplerInfo.unnormalizedCoordinates = VK_FALSE;
272 samplerInfo.compareEnable = VK_FALSE;
273 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
274 VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo,
nullptr, &spriteSampler));
277 void VK_Sprite3D::createDescriptorSetLayout() {
278 if (descriptorSetLayout != VK_NULL_HANDLE) {
282 std::array<VkDescriptorSetLayoutBinding, 2> bindings{};
283 bindings[0].binding = 0;
284 bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
285 bindings[0].descriptorCount = 1;
286 bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
288 bindings[1].binding = 1;
289 bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
290 bindings[1].descriptorCount = 1;
291 bindings[1].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
293 VkDescriptorSetLayoutCreateInfo layoutInfo{};
294 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
295 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
296 layoutInfo.pBindings = bindings.data();
297 VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &layoutInfo,
nullptr, &descriptorSetLayout));
300 void VK_Sprite3D::createCameraBuffers() {
301 cameraBuffers.resize(imageCount, VK_NULL_HANDLE);
302 cameraBufferMemory.resize(imageCount, VK_NULL_HANDLE);
303 cameraBuffersMapped.resize(imageCount,
nullptr);
305 for (
size_t i = 0; i < imageCount; ++i) {
306 createBuffer(
sizeof(CameraUBO), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, cameraBuffers[i], cameraBufferMemory[i]);
307 VK_CHECK_RESULT(vkMapMemory(device, cameraBufferMemory[i], 0,
sizeof(CameraUBO), 0, &cameraBuffersMapped[i]));
309 std::memcpy(cameraBuffersMapped[i], &camera,
sizeof(camera));
313 void VK_Sprite3D::destroyCameraBuffers() {
314 for (
size_t i = 0; i < cameraBuffers.size(); ++i) {
315 if (cameraBuffersMapped[i] !=
nullptr) {
316 vkUnmapMemory(device, cameraBufferMemory[i]);
318 if (cameraBuffers[i] != VK_NULL_HANDLE) {
319 vkDestroyBuffer(device, cameraBuffers[i],
nullptr);
321 if (cameraBufferMemory[i] != VK_NULL_HANDLE) {
322 vkFreeMemory(device, cameraBufferMemory[i],
nullptr);
325 cameraBuffers.clear();
326 cameraBufferMemory.clear();
327 cameraBuffersMapped.clear();
330 void VK_Sprite3D::createDescriptorPool() {
331 std::array<VkDescriptorPoolSize, 2> poolSizes{};
332 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
333 poolSizes[0].descriptorCount =
static_cast<uint32_t
>(imageCount);
334 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
335 poolSizes[1].descriptorCount =
static_cast<uint32_t
>(imageCount);
337 VkDescriptorPoolCreateInfo poolInfo{};
338 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
339 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
340 poolInfo.pPoolSizes = poolSizes.data();
341 poolInfo.maxSets =
static_cast<uint32_t
>(imageCount);
342 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo,
nullptr, &descriptorPool));
345 void VK_Sprite3D::createDescriptorSets() {
346 descriptorSets.resize(imageCount, VK_NULL_HANDLE);
347 std::vector<VkDescriptorSetLayout> layouts(imageCount, descriptorSetLayout);
349 VkDescriptorSetAllocateInfo allocInfo{};
350 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
351 allocInfo.descriptorPool = descriptorPool;
352 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(imageCount);
353 allocInfo.pSetLayouts = layouts.data();
354 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()));
356 for (
size_t i = 0; i < imageCount; ++i) {
357 VkDescriptorImageInfo imageInfo{};
358 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
359 imageInfo.imageView = spriteImageView;
360 imageInfo.sampler = spriteSampler;
362 VkDescriptorBufferInfo bufferInfo{};
363 bufferInfo.buffer = cameraBuffers[i];
364 bufferInfo.offset = 0;
365 bufferInfo.range =
sizeof(CameraUBO);
367 std::array<VkWriteDescriptorSet, 2> writes{};
368 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
369 writes[0].dstSet = descriptorSets[i];
370 writes[0].dstBinding = 0;
371 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
372 writes[0].descriptorCount = 1;
373 writes[0].pImageInfo = &imageInfo;
375 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
376 writes[1].dstSet = descriptorSets[i];
377 writes[1].dstBinding = 1;
378 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
379 writes[1].descriptorCount = 1;
380 writes[1].pBufferInfo = &bufferInfo;
382 vkUpdateDescriptorSets(device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
386 void VK_Sprite3D::createPipeline() {
389 auto vertCode = readShaderFile(vertexShaderPath);
390 auto fragCode = readShaderFile(fragmentShaderPath);
394 VkPipelineShaderStageCreateInfo vertStage{};
395 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
396 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
397 vertStage.module = vertModule;
398 vertStage.pName =
"main";
400 VkPipelineShaderStageCreateInfo fragStage{};
401 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
402 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
403 fragStage.module = fragModule;
404 fragStage.pName =
"main";
406 VkPipelineShaderStageCreateInfo stages[] = {vertStage, fragStage};
408 VkVertexInputBindingDescription binding{};
410 binding.stride =
sizeof(Vertex);
411 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
413 std::array<VkVertexInputAttributeDescription, 2> attributes{};
414 attributes[0].binding = 0;
415 attributes[0].location = 0;
416 attributes[0].format = VK_FORMAT_R32G32_SFLOAT;
417 attributes[0].offset = offsetof(Vertex, pos);
418 attributes[1].binding = 0;
419 attributes[1].location = 1;
420 attributes[1].format = VK_FORMAT_R32G32_SFLOAT;
421 attributes[1].offset = offsetof(Vertex, uv);
423 VkPipelineVertexInputStateCreateInfo vertexInput{};
424 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
425 vertexInput.vertexBindingDescriptionCount = 1;
426 vertexInput.pVertexBindingDescriptions = &binding;
427 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
428 vertexInput.pVertexAttributeDescriptions = attributes.data();
430 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
431 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
432 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
434 std::array<VkDynamicState, 2> dynamicStates{{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}};
435 VkPipelineDynamicStateCreateInfo dynamicState{};
436 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
437 dynamicState.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
438 dynamicState.pDynamicStates = dynamicStates.data();
440 VkPipelineViewportStateCreateInfo viewportState{};
441 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
442 viewportState.viewportCount = 1;
443 viewportState.scissorCount = 1;
445 VkPipelineRasterizationStateCreateInfo rasterizer{};
446 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
447 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
448 rasterizer.cullMode = VK_CULL_MODE_NONE;
449 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
450 rasterizer.lineWidth = 1.0f;
452 VkPipelineMultisampleStateCreateInfo multisampling{};
453 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
454 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
456 VkPipelineDepthStencilStateCreateInfo depthStencil{};
457 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
458 depthStencil.depthTestEnable = depthTestEnabled ? VK_TRUE : VK_FALSE;
459 depthStencil.depthWriteEnable = depthWriteEnabled ? VK_TRUE : VK_FALSE;
460 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
462 VkPipelineColorBlendAttachmentState blendAttachment{};
463 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
464 blendAttachment.blendEnable = VK_TRUE;
465 blendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
466 blendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
467 blendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
468 blendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
469 blendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
470 blendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
472 VkPipelineColorBlendStateCreateInfo colorBlending{};
473 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
474 colorBlending.attachmentCount = 1;
475 colorBlending.pAttachments = &blendAttachment;
477 VkPushConstantRange pushRange{};
478 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
479 pushRange.offset = 0;
480 pushRange.size =
sizeof(glm::vec4) * 3;
482 VkPipelineLayoutCreateInfo layoutInfo{};
483 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
484 layoutInfo.setLayoutCount = 1;
485 layoutInfo.pSetLayouts = &descriptorSetLayout;
486 layoutInfo.pushConstantRangeCount = 1;
487 layoutInfo.pPushConstantRanges = &pushRange;
488 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &layoutInfo,
nullptr, &pipelineLayout));
490 VkPipelineRenderingCreateInfo renderingInfo{};
491 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
492 renderingInfo.colorAttachmentCount = 1;
493 renderingInfo.pColorAttachmentFormats = &colorAttachmentFormat;
494 if (depthAttachmentFormat != VK_FORMAT_UNDEFINED) {
495 renderingInfo.depthAttachmentFormat = depthAttachmentFormat;
498 VkGraphicsPipelineCreateInfo pipelineInfo{};
499 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
500 pipelineInfo.pNext = &renderingInfo;
501 pipelineInfo.stageCount = 2;
502 pipelineInfo.pStages = stages;
503 pipelineInfo.pVertexInputState = &vertexInput;
504 pipelineInfo.pInputAssemblyState = &inputAssembly;
505 pipelineInfo.pViewportState = &viewportState;
506 pipelineInfo.pRasterizationState = &rasterizer;
507 pipelineInfo.pMultisampleState = &multisampling;
508 pipelineInfo.pDepthStencilState = &depthStencil;
509 pipelineInfo.pColorBlendState = &colorBlending;
510 pipelineInfo.pDynamicState = &dynamicState;
511 pipelineInfo.layout = pipelineLayout;
512 pipelineInfo.renderPass = VK_NULL_HANDLE;
513 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo,
nullptr, &pipeline));
515 vkDestroyShaderModule(device, fragModule,
nullptr);
516 vkDestroyShaderModule(device, vertModule,
nullptr);
519 void VK_Sprite3D::destroyPipeline() {
520 if (pipeline != VK_NULL_HANDLE) {
521 vkDestroyPipeline(device, pipeline,
nullptr);
522 pipeline = VK_NULL_HANDLE;
524 if (pipelineLayout != VK_NULL_HANDLE) {
525 vkDestroyPipelineLayout(device, pipelineLayout,
nullptr);
526 pipelineLayout = VK_NULL_HANDLE;
530 void VK_Sprite3D::destroyTexture() {
531 if (spriteSampler != VK_NULL_HANDLE) {
532 vkDestroySampler(device, spriteSampler,
nullptr);
533 spriteSampler = VK_NULL_HANDLE;
535 if (spriteImageView != VK_NULL_HANDLE) {
536 vkDestroyImageView(device, spriteImageView,
nullptr);
537 spriteImageView = VK_NULL_HANDLE;
539 if (spriteImage != VK_NULL_HANDLE) {
540 vkDestroyImage(device, spriteImage,
nullptr);
541 spriteImage = VK_NULL_HANDLE;
543 if (spriteImageMemory != VK_NULL_HANDLE) {
544 vkFreeMemory(device, spriteImageMemory,
nullptr);
545 spriteImageMemory = VK_NULL_HANDLE;
551 void VK_Sprite3D::destroyBuffers() {
552 if (vertexBuffer != VK_NULL_HANDLE) {
553 vkDestroyBuffer(device, vertexBuffer,
nullptr);
554 vertexBuffer = VK_NULL_HANDLE;
556 if (vertexBufferMemory != VK_NULL_HANDLE) {
557 vkFreeMemory(device, vertexBufferMemory,
nullptr);
558 vertexBufferMemory = VK_NULL_HANDLE;
560 if (indexBuffer != VK_NULL_HANDLE) {
561 vkDestroyBuffer(device, indexBuffer,
nullptr);
562 indexBuffer = VK_NULL_HANDLE;
564 if (indexBufferMemory != VK_NULL_HANDLE) {
565 vkFreeMemory(device, indexBufferMemory,
nullptr);
566 indexBufferMemory = VK_NULL_HANDLE;
570 void VK_Sprite3D::destroyDescriptors() {
571 descriptorSets.clear();
572 if (descriptorPool != VK_NULL_HANDLE) {
573 vkDestroyDescriptorPool(device, descriptorPool,
nullptr);
574 descriptorPool = VK_NULL_HANDLE;
576 if (descriptorSetLayout != VK_NULL_HANDLE) {
577 vkDestroyDescriptorSetLayout(device, descriptorSetLayout,
nullptr);
578 descriptorSetLayout = VK_NULL_HANDLE;
582 void VK_Sprite3D::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
const {
583 VkBuffer newBuffer = VK_NULL_HANDLE;
584 VkDeviceMemory newMemory = VK_NULL_HANDLE;
586 VkBufferCreateInfo bufferInfo{};
587 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
588 bufferInfo.size = size;
589 bufferInfo.usage = usage;
590 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
593 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo,
nullptr, &newBuffer));
595 VkMemoryRequirements memRequirements{};
596 vkGetBufferMemoryRequirements(device, newBuffer, &memRequirements);
598 VkMemoryAllocateInfo allocInfo{};
599 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
600 allocInfo.allocationSize = memRequirements.size;
601 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
602 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
605 if (newBuffer != VK_NULL_HANDLE) {
606 vkDestroyBuffer(device, newBuffer,
nullptr);
608 if (newMemory != VK_NULL_HANDLE) {
609 vkFreeMemory(device, newMemory,
nullptr);
614 if (buffer != VK_NULL_HANDLE) {
615 vkDestroyBuffer(device, buffer,
nullptr);
617 if (bufferMemory != VK_NULL_HANDLE) {
618 vkFreeMemory(device, bufferMemory,
nullptr);
621 bufferMemory = newMemory;
624 uint32_t VK_Sprite3D::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
625 VkPhysicalDeviceMemoryProperties memProperties{};
626 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
627 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
628 if ((typeFilter & (1U << i)) != 0U && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
632 throw mxvk::Exception(
"Failed to find suitable memory type for 3D sprite");
635 VkCommandBuffer VK_Sprite3D::beginSingleTimeCommands()
const {
636 VkCommandBufferAllocateInfo allocInfo{};
637 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
638 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
639 allocInfo.commandPool = commandPool;
640 allocInfo.commandBufferCount = 1;
642 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
643 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
645 VkCommandBufferBeginInfo beginInfo{};
646 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
647 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
649 return commandBuffer;
652 void VK_Sprite3D::endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
655 VkSubmitInfo submitInfo{};
656 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
657 submitInfo.commandBufferCount = 1;
658 submitInfo.pCommandBuffers = &commandBuffer;
659 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE));
661 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
664 void VK_Sprite3D::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &imageMemory)
const {
665 VkImage newImage = VK_NULL_HANDLE;
666 VkDeviceMemory newMemory = VK_NULL_HANDLE;
668 VkImageCreateInfo imageInfo{};
669 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
670 imageInfo.imageType = VK_IMAGE_TYPE_2D;
671 imageInfo.extent.width = width;
672 imageInfo.extent.height = height;
673 imageInfo.extent.depth = 1;
674 imageInfo.mipLevels = 1;
675 imageInfo.arrayLayers = 1;
676 imageInfo.format = format;
677 imageInfo.tiling = tiling;
678 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
679 imageInfo.usage = usage;
680 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
681 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
684 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo,
nullptr, &newImage));
686 VkMemoryRequirements memRequirements{};
687 vkGetImageMemoryRequirements(device, newImage, &memRequirements);
689 VkMemoryAllocateInfo allocInfo{};
690 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
691 allocInfo.allocationSize = memRequirements.size;
692 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
693 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
696 if (newImage != VK_NULL_HANDLE) {
697 vkDestroyImage(device, newImage,
nullptr);
699 if (newMemory != VK_NULL_HANDLE) {
700 vkFreeMemory(device, newMemory,
nullptr);
705 if (image != VK_NULL_HANDLE) {
706 vkDestroyImage(device, image,
nullptr);
708 if (imageMemory != VK_NULL_HANDLE) {
709 vkFreeMemory(device, imageMemory,
nullptr);
712 imageMemory = newMemory;
715 VkImageView VK_Sprite3D::createImageView(VkImage image, VkFormat format)
const {
716 VkImageViewCreateInfo viewInfo{};
717 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
718 viewInfo.image = image;
719 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
720 viewInfo.format = format;
721 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
722 viewInfo.subresourceRange.levelCount = 1;
723 viewInfo.subresourceRange.layerCount = 1;
725 VkImageView imageView = VK_NULL_HANDLE;
726 VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo,
nullptr, &imageView));
730 void VK_Sprite3D::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
731 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
733 VkImageMemoryBarrier barrier{};
734 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
735 barrier.oldLayout = oldLayout;
736 barrier.newLayout = newLayout;
737 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
738 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
739 barrier.image = image;
740 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
741 barrier.subresourceRange.levelCount = 1;
742 barrier.subresourceRange.layerCount = 1;
744 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
745 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
747 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
748 barrier.srcAccessMask = 0;
749 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
750 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
751 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
752 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
753 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
754 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
756 throw mxvk::Exception(
"Unsupported 3D sprite image layout transition");
759 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
760 endSingleTimeCommands(commandBuffer);
763 void VK_Sprite3D::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
764 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
766 VkBufferImageCopy region{};
767 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
768 region.imageSubresource.layerCount = 1;
769 region.imageExtent = {width, height, 1};
771 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
772 endSingleTimeCommands(commandBuffer);
775 SDL_Surface *VK_Sprite3D::convertToRGBA(SDL_Surface *surface)
const {
776 if (surface ==
nullptr) {
779 return SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
782 std::vector<char> VK_Sprite3D::readShaderFile(
const std::string &path)
const {
783 std::ifstream file(path, std::ios::ate | std::ios::binary);
784 if (!file.is_open()) {
785 throw mxvk::Exception(
"Failed to open 3D sprite shader: " + path);
788 const std::streamsize fileSize = file.tellg();
789 if (fileSize <= 0 || (fileSize % 4) != 0) {
790 throw mxvk::Exception(
"Invalid 3D sprite shader size: " + path);
793 std::vector<char> buffer(
static_cast<size_t>(fileSize));
795 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.