19 : device(dev), physicalDevice(physDev), graphicsQueue(gQueue), commandPool(cmdPool) {
20 std::cout <<
"mxvk: Created Sprite\n";
24 destroyStagingResources();
28 if (pool == commandPool) {
34 destroyStagingResources();
39 if (filter != VK_FILTER_NEAREST && filter != VK_FILTER_LINEAR) {
40 throw mxvk::Exception(
"VKSprite::setTextureFilter supports only nearest or linear filtering");
42 if (textureFilter == filter) {
46 textureFilter = filter;
48 destroyTextureDescriptorPools();
54 std::cout <<
"mxvk: Sprite texture filter set to "
55 << (textureFilter == VK_FILTER_NEAREST ?
"nearest\n" :
"linear\n");
59 vkDeviceWaitIdle(device);
62 destroyStagingResources();
66 if (quadVertexBuffer != VK_NULL_HANDLE) {
67 std::cout <<
"vk: destroying sprite quad vertex buffer\n";
68 vkDestroyBuffer(device, quadVertexBuffer,
nullptr);
69 vkFreeMemory(device, quadVertexBufferMemory,
nullptr);
71 if (quadIndexBuffer != VK_NULL_HANDLE) {
72 std::cout <<
"vk: destroying sprite quad index buffer\n";
73 vkDestroyBuffer(device, quadIndexBuffer,
nullptr);
74 vkFreeMemory(device, quadIndexBufferMemory,
nullptr);
77 destroyTextureDescriptorPools();
80 std::cout <<
"vk: destroying sprite sampler\n";
84 if (!externalTexture && spriteImageView != VK_NULL_HANDLE) {
85 std::cout <<
"vk: destroying sprite image view\n";
86 vkDestroyImageView(device, spriteImageView,
nullptr);
89 if (!externalTexture && spriteImage != VK_NULL_HANDLE) {
90 std::cout <<
"vk: destroying sprite image\n";
91 vkDestroyImage(device, spriteImage,
nullptr);
92 vkFreeMemory(device, spriteImageMemory,
nullptr);
95 if (fragmentShaderModule != VK_NULL_HANDLE) {
96 vkDestroyShaderModule(device, fragmentShaderModule,
nullptr);
99 if (customPipeline != VK_NULL_HANDLE) {
100 std::cout <<
"vk: destroying sprite custom pipeline\n";
101 vkDestroyPipeline(device, customPipeline,
nullptr);
104 if (customPipelineLayout != VK_NULL_HANDLE) {
105 std::cout <<
"vk: destroying sprite custom pipeline layout\n";
106 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
109 destroyExtendedUBO();
110 destroyInstanceResources();
113 void VK_Sprite::destroySpriteResources() {
114 destroyStagingResources();
116 destroyCudaInterop();
119 destroyTextureDescriptorPools();
122 std::cout <<
"vk: destroying sprite sampler\n";
127 if (!externalTexture && spriteImageView != VK_NULL_HANDLE) {
128 std::cout <<
"vk: destroying sprite image view\n";
129 vkDestroyImageView(device, spriteImageView,
nullptr);
130 spriteImageView = VK_NULL_HANDLE;
133 if (!externalTexture && spriteImage != VK_NULL_HANDLE) {
134 std::cout <<
"vk: destroying sprite image\n";
135 vkDestroyImage(device, spriteImage,
nullptr);
136 spriteImage = VK_NULL_HANDLE;
138 if (!externalTexture && spriteImageMemory != VK_NULL_HANDLE) {
139 vkFreeMemory(device, spriteImageMemory,
nullptr);
140 spriteImageMemory = VK_NULL_HANDLE;
142 externalTexture =
false;
144 if (fragmentShaderModule != VK_NULL_HANDLE) {
145 vkDestroyShaderModule(device, fragmentShaderModule,
nullptr);
146 fragmentShaderModule = VK_NULL_HANDLE;
149 if (customPipeline != VK_NULL_HANDLE) {
150 std::cout <<
"vk: destroying sprite custom pipeline\n";
151 vkDestroyPipeline(device, customPipeline,
nullptr);
152 customPipeline = VK_NULL_HANDLE;
155 if (customPipelineLayout != VK_NULL_HANDLE) {
156 std::cout <<
"vk: destroying sprite custom pipeline layout\n";
157 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
158 customPipelineLayout = VK_NULL_HANDLE;
161 hasCustomShader =
false;
162 spriteLoaded =
false;
166 if (extendedUBOEnabled)
168 extendedUBOEnabled =
true;
170 createExtendedDescriptorSetLayout();
175 extendedUBOData.mouse = glm::vec4(
mx, my, pressed, reserved);
179 extendedUBOData.u0 = glm::vec4(x, y, z, w);
183 extendedUBOData.u1 = glm::vec4(x, y, z, w);
187 extendedUBOData.u2 = glm::vec4(x, y, z, w);
191 extendedUBOData.u3 = glm::vec4(x, y, z, w);
194 void VK_Sprite::createExtendedUBO() {
195 if (extendedUBOBuffer != VK_NULL_HANDLE)
197 createBuffer(
sizeof(SpriteExtendedUBO), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
198 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
199 extendedUBOBuffer, extendedUBOMemory);
200 VK_CHECK_RESULT(vkMapMemory(device, extendedUBOMemory, 0,
sizeof(SpriteExtendedUBO), 0, &extendedUBOMapped));
201 memset(extendedUBOMapped, 0,
sizeof(SpriteExtendedUBO));
204 void VK_Sprite::updateExtendedUBO() {
205 if (!extendedUBOEnabled || !extendedUBOMapped)
207 memcpy(extendedUBOMapped, &extendedUBOData,
sizeof(SpriteExtendedUBO));
210 void VK_Sprite::createExtendedDescriptorSetLayout() {
211 if (extendedDescriptorSetLayout != VK_NULL_HANDLE)
214 std::array<VkDescriptorSetLayoutBinding, 2> bindings{};
216 bindings[0].binding = 0;
217 bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
218 bindings[0].descriptorCount = 1;
219 bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
220 bindings[0].pImmutableSamplers =
nullptr;
222 bindings[1].binding = 1;
223 bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
224 bindings[1].descriptorCount = 1;
225 bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
226 bindings[1].pImmutableSamplers =
nullptr;
228 VkDescriptorSetLayoutCreateInfo layoutInfo{};
229 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
230 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
231 layoutInfo.pBindings = bindings.data();
233 VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &layoutInfo,
nullptr, &extendedDescriptorSetLayout));
234 ownExtendedDescriptorSetLayout =
true;
237 void VK_Sprite::createExtendedDescriptorSet() {
238 if (extendedDescriptorSetLayout == VK_NULL_HANDLE || spriteImageView == VK_NULL_HANDLE ||
239 spriteSampler == VK_NULL_HANDLE || extendedUBOBuffer == VK_NULL_HANDLE)
242 if (extendedDescriptorPool != VK_NULL_HANDLE) {
243 vkDeviceWaitIdle(device);
244 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
245 extendedDescriptorPool = VK_NULL_HANDLE;
246 extendedDescriptorSet = VK_NULL_HANDLE;
249 std::array<VkDescriptorPoolSize, 2> poolSizes{};
250 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
251 poolSizes[0].descriptorCount = 1;
252 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
253 poolSizes[1].descriptorCount = 1;
255 VkDescriptorPoolCreateInfo poolInfo{};
256 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
257 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
258 poolInfo.pPoolSizes = poolSizes.data();
259 poolInfo.maxSets = 1;
261 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo,
nullptr, &extendedDescriptorPool));
263 VkDescriptorSetAllocateInfo allocInfo{};
264 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
265 allocInfo.descriptorPool = extendedDescriptorPool;
266 allocInfo.descriptorSetCount = 1;
267 allocInfo.pSetLayouts = &extendedDescriptorSetLayout;
269 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &extendedDescriptorSet));
271 VkDescriptorImageInfo imageInfo{};
272 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
273 imageInfo.imageView = spriteImageView;
276 VkDescriptorBufferInfo bufferInfo{};
277 bufferInfo.buffer = extendedUBOBuffer;
278 bufferInfo.offset = 0;
279 bufferInfo.range =
sizeof(SpriteExtendedUBO);
281 std::array<VkWriteDescriptorSet, 2> writes{};
282 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
283 writes[0].dstSet = extendedDescriptorSet;
284 writes[0].dstBinding = 0;
285 writes[0].dstArrayElement = 0;
286 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
287 writes[0].descriptorCount = 1;
288 writes[0].pImageInfo = &imageInfo;
290 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
291 writes[1].dstSet = extendedDescriptorSet;
292 writes[1].dstBinding = 1;
293 writes[1].dstArrayElement = 0;
294 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
295 writes[1].descriptorCount = 1;
296 writes[1].pBufferInfo = &bufferInfo;
298 vkUpdateDescriptorSets(device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
301 void VK_Sprite::destroyExtendedUBO() {
302 if (extendedDescriptorPool != VK_NULL_HANDLE) {
303 vkDeviceWaitIdle(device);
304 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
305 extendedDescriptorPool = VK_NULL_HANDLE;
306 extendedDescriptorSet = VK_NULL_HANDLE;
308 if (ownExtendedDescriptorSetLayout && extendedDescriptorSetLayout != VK_NULL_HANDLE) {
309 vkDestroyDescriptorSetLayout(device, extendedDescriptorSetLayout,
nullptr);
310 extendedDescriptorSetLayout = VK_NULL_HANDLE;
311 ownExtendedDescriptorSetLayout =
false;
313 if (extendedUBOBuffer != VK_NULL_HANDLE) {
314 if (extendedUBOMapped) {
315 vkUnmapMemory(device, extendedUBOMemory);
316 extendedUBOMapped =
nullptr;
318 vkDestroyBuffer(device, extendedUBOBuffer,
nullptr);
319 vkFreeMemory(device, extendedUBOMemory,
nullptr);
320 extendedUBOBuffer = VK_NULL_HANDLE;
321 extendedUBOMemory = VK_NULL_HANDLE;
323 extendedUBOEnabled =
false;
326 VkDeviceSize VK_Sprite::stagingAllocationSize(VkDeviceSize requiredSize)
const {
327 VkDeviceSize allocationSize = 1;
328 while (allocationSize < requiredSize && allocationSize <= (std::numeric_limits<VkDeviceSize>::max() / 2)) {
331 return std::max(allocationSize, requiredSize);
334 void VK_Sprite::createStagingResources(VkDeviceSize size) {
335 const VkDeviceSize allocationSize = stagingAllocationSize(size);
336 if (stagingResourcesCreated && persistentStagingSize >= size) {
339 destroyStagingResources();
341 createBuffer(allocationSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
342 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
343 persistentStagingBuffer, persistentStagingMemory);
346 VK_CHECK_RESULT(vkMapMemory(device, persistentStagingMemory, 0, allocationSize, 0, &persistentStagingMapped));
347 persistentStagingSize = allocationSize;
348 VkCommandBufferAllocateInfo allocInfo{};
349 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
350 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
351 allocInfo.commandPool = commandPool;
352 allocInfo.commandBufferCount = 1;
353 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &uploadCmdBuffer));
354 VkFenceCreateInfo fenceInfo{};
355 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
356 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
357 VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo,
nullptr, &uploadFence));
358 stagingResourcesCreated =
true;
360 if (uploadCmdBuffer != VK_NULL_HANDLE) {
361 vkFreeCommandBuffers(device, commandPool, 1, &uploadCmdBuffer);
362 uploadCmdBuffer = VK_NULL_HANDLE;
364 if (persistentStagingMapped) {
365 vkUnmapMemory(device, persistentStagingMemory);
366 persistentStagingMapped =
nullptr;
368 if (persistentStagingBuffer != VK_NULL_HANDLE) {
369 vkDestroyBuffer(device, persistentStagingBuffer,
nullptr);
370 persistentStagingBuffer = VK_NULL_HANDLE;
372 if (persistentStagingMemory != VK_NULL_HANDLE) {
373 vkFreeMemory(device, persistentStagingMemory,
nullptr);
374 persistentStagingMemory = VK_NULL_HANDLE;
376 persistentStagingSize = 0;
381 void VK_Sprite::destroyStagingResources() {
382 if (!stagingResourcesCreated)
385 if (uploadFence != VK_NULL_HANDLE) {
386 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
387 vkDestroyFence(device, uploadFence,
nullptr);
388 uploadFence = VK_NULL_HANDLE;
390 if (uploadCmdBuffer != VK_NULL_HANDLE) {
391 vkFreeCommandBuffers(device, commandPool, 1, &uploadCmdBuffer);
392 uploadCmdBuffer = VK_NULL_HANDLE;
394 if (persistentStagingBuffer != VK_NULL_HANDLE) {
395 vkUnmapMemory(device, persistentStagingMemory);
396 vkDestroyBuffer(device, persistentStagingBuffer,
nullptr);
397 vkFreeMemory(device, persistentStagingMemory,
nullptr);
398 persistentStagingBuffer = VK_NULL_HANDLE;
399 persistentStagingMemory = VK_NULL_HANDLE;
400 persistentStagingMapped =
nullptr;
401 persistentStagingSize = 0;
403 stagingResourcesCreated =
false;
406 void VK_Sprite::destroyInstanceResources() {
407 if (instanceBuffer != VK_NULL_HANDLE) {
408 if (instanceBufferMapped) {
409 vkUnmapMemory(device, instanceBufferMemory);
410 instanceBufferMapped =
nullptr;
412 vkDestroyBuffer(device, instanceBuffer,
nullptr);
413 vkFreeMemory(device, instanceBufferMemory,
nullptr);
414 instanceBuffer = VK_NULL_HANDLE;
415 instanceBufferMemory = VK_NULL_HANDLE;
416 instanceBufferCapacity = 0;
418 if (instancedPipeline != VK_NULL_HANDLE) {
419 vkDestroyPipeline(device, instancedPipeline,
nullptr);
420 instancedPipeline = VK_NULL_HANDLE;
422 if (instancedPipelineLayout != VK_NULL_HANDLE) {
423 vkDestroyPipelineLayout(device, instancedPipelineLayout,
nullptr);
424 instancedPipelineLayout = VK_NULL_HANDLE;
426 instancingEnabled =
false;
429 void VK_Sprite::ensureInstanceBuffer(uint32_t count) {
430 if (instanceBufferCapacity >= count && instanceBuffer != VK_NULL_HANDLE)
433 if (instanceBuffer != VK_NULL_HANDLE) {
434 if (instanceBufferMapped) {
435 vkUnmapMemory(device, instanceBufferMemory);
436 instanceBufferMapped =
nullptr;
438 vkDestroyBuffer(device, instanceBuffer,
nullptr);
439 vkFreeMemory(device, instanceBufferMemory,
nullptr);
440 instanceBuffer = VK_NULL_HANDLE;
441 instanceBufferMemory = VK_NULL_HANDLE;
444 VkDeviceSize size =
sizeof(SpriteInstanceData) * count;
445 createBuffer(size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
446 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
447 instanceBuffer, instanceBufferMemory);
449 VK_CHECK_RESULT(vkMapMemory(device, instanceBufferMemory, 0, size, 0, &instanceBufferMapped));
450 instanceBufferCapacity = count;
454 const std::string &instanceVertShaderPath,
455 const std::string &instanceFragShaderPath) {
456 if (colorAttachmentFormat == VK_FORMAT_UNDEFINED || descriptorSetLayout == VK_NULL_HANDLE) {
457 throw mxvk::Exception(
"VKSprite::enableInstancing called before color format/descriptorSetLayout set");
459 ensureInstanceBuffer(maxInstances);
461 instanceVertPath = instanceVertShaderPath;
462 instanceFragPath = instanceFragShaderPath;
463 createInstancedPipeline(instanceVertShaderPath, instanceFragShaderPath);
464 instancingEnabled =
true;
465 std::cout << std::format(
"mxvk: Instancing enabled (max {} instances)\n", maxInstances);
468 void VK_Sprite::createInstancedPipeline(
const std::string &vertPath,
const std::string &fragPath) {
469 if (instancedPipeline != VK_NULL_HANDLE) {
470 vkDestroyPipeline(device, instancedPipeline,
nullptr);
471 instancedPipeline = VK_NULL_HANDLE;
473 if (instancedPipelineLayout != VK_NULL_HANDLE) {
474 vkDestroyPipelineLayout(device, instancedPipelineLayout,
nullptr);
475 instancedPipelineLayout = VK_NULL_HANDLE;
478 auto vertShaderCode = readShaderFile(vertPath);
479 auto fragShaderCode = readShaderFile(fragPath);
483 VkPipelineShaderStageCreateInfo vertStageInfo{};
484 vertStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
485 vertStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
486 vertStageInfo.module = vertModule;
487 vertStageInfo.pName =
"main";
489 VkPipelineShaderStageCreateInfo fragStageInfo{};
490 fragStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
491 fragStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
492 fragStageInfo.module = fragModule;
493 fragStageInfo.pName =
"main";
495 VkPipelineShaderStageCreateInfo shaderStages[] = {vertStageInfo, fragStageInfo};
497 std::array<VkVertexInputBindingDescription, 2> bindingDescs{};
498 bindingDescs[0].binding = 0;
499 bindingDescs[0].stride =
sizeof(float) * 4;
500 bindingDescs[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
501 bindingDescs[1].binding = 1;
502 bindingDescs[1].stride =
sizeof(SpriteInstanceData);
503 bindingDescs[1].inputRate = VK_VERTEX_INPUT_RATE_INSTANCE;
505 std::array<VkVertexInputAttributeDescription, 4> attrDescs{};
507 attrDescs[0].binding = 0;
508 attrDescs[0].location = 0;
509 attrDescs[0].format = VK_FORMAT_R32G32_SFLOAT;
510 attrDescs[0].offset = 0;
512 attrDescs[1].binding = 0;
513 attrDescs[1].location = 1;
514 attrDescs[1].format = VK_FORMAT_R32G32_SFLOAT;
515 attrDescs[1].offset =
sizeof(float) * 2;
517 attrDescs[2].binding = 1;
518 attrDescs[2].location = 2;
519 attrDescs[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
520 attrDescs[2].offset = 0;
522 attrDescs[3].binding = 1;
523 attrDescs[3].location = 3;
524 attrDescs[3].format = VK_FORMAT_R32G32B32A32_SFLOAT;
525 attrDescs[3].offset =
sizeof(float) * 4;
527 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
528 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
529 vertexInputInfo.vertexBindingDescriptionCount =
static_cast<uint32_t
>(bindingDescs.size());
530 vertexInputInfo.pVertexBindingDescriptions = bindingDescs.data();
531 vertexInputInfo.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrDescs.size());
532 vertexInputInfo.pVertexAttributeDescriptions = attrDescs.data();
534 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
535 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
536 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
537 inputAssembly.primitiveRestartEnable = VK_FALSE;
539 std::vector<VkDynamicState> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
540 VkPipelineDynamicStateCreateInfo dynamicState{};
541 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
542 dynamicState.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
543 dynamicState.pDynamicStates = dynamicStates.data();
545 VkPipelineViewportStateCreateInfo viewportState{};
546 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
547 viewportState.viewportCount = 1;
548 viewportState.scissorCount = 1;
550 VkPipelineRasterizationStateCreateInfo rasterizer{};
551 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
552 rasterizer.depthClampEnable = VK_FALSE;
553 rasterizer.rasterizerDiscardEnable = VK_FALSE;
554 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
555 rasterizer.lineWidth = 1.0f;
556 rasterizer.cullMode = VK_CULL_MODE_NONE;
557 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
558 rasterizer.depthBiasEnable = VK_FALSE;
560 VkPipelineMultisampleStateCreateInfo multisampling{};
561 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
562 multisampling.sampleShadingEnable = VK_FALSE;
563 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
565 VkPipelineDepthStencilStateCreateInfo depthStencil{};
566 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
567 depthStencil.depthTestEnable = VK_FALSE;
568 depthStencil.depthWriteEnable = VK_FALSE;
570 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
571 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
572 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
573 colorBlendAttachment.blendEnable = VK_TRUE;
574 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
575 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
576 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
577 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
578 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
579 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
581 VkPipelineColorBlendStateCreateInfo colorBlending{};
582 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
583 colorBlending.logicOpEnable = VK_FALSE;
584 colorBlending.attachmentCount = 1;
585 colorBlending.pAttachments = &colorBlendAttachment;
587 VkPushConstantRange pushConstantRange{};
588 pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
589 pushConstantRange.offset = 0;
590 pushConstantRange.size =
sizeof(float) * 2;
592 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
593 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
594 pipelineLayoutInfo.setLayoutCount = 1;
595 pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
596 pipelineLayoutInfo.pushConstantRangeCount = 1;
597 pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
599 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo,
nullptr, &instancedPipelineLayout));
601 VkGraphicsPipelineCreateInfo pipelineInfo{};
602 VkPipelineRenderingCreateInfo renderingInfo{};
603 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
604 renderingInfo.viewMask = 0;
605 renderingInfo.colorAttachmentCount = 1;
606 renderingInfo.pColorAttachmentFormats = &colorAttachmentFormat;
607 if (depthAttachmentFormat != VK_FORMAT_UNDEFINED) {
608 renderingInfo.depthAttachmentFormat = depthAttachmentFormat;
611 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
612 pipelineInfo.pNext = &renderingInfo;
613 pipelineInfo.stageCount = 2;
614 pipelineInfo.pStages = shaderStages;
615 pipelineInfo.pVertexInputState = &vertexInputInfo;
616 pipelineInfo.pInputAssemblyState = &inputAssembly;
617 pipelineInfo.pViewportState = &viewportState;
618 pipelineInfo.pRasterizationState = &rasterizer;
619 pipelineInfo.pMultisampleState = &multisampling;
620 pipelineInfo.pDepthStencilState = &depthStencil;
621 pipelineInfo.pColorBlendState = &colorBlending;
622 pipelineInfo.pDynamicState = &dynamicState;
623 pipelineInfo.layout = instancedPipelineLayout;
624 pipelineInfo.renderPass = VK_NULL_HANDLE;
625 pipelineInfo.subpass = 0;
626 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
628 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo,
nullptr, &instancedPipeline));
630 vkDestroyShaderModule(device, vertModule,
nullptr);
631 vkDestroyShaderModule(device, fragModule,
nullptr);
634 void VK_Sprite::createCustomPipeline() {
635 if (!hasCustomShader || fragmentShaderModule == VK_NULL_HANDLE)
637 if (colorAttachmentFormat == VK_FORMAT_UNDEFINED || descriptorSetLayout == VK_NULL_HANDLE)
640 if (customPipeline != VK_NULL_HANDLE) {
641 vkDestroyPipeline(device, customPipeline,
nullptr);
642 customPipeline = VK_NULL_HANDLE;
644 if (customPipelineLayout != VK_NULL_HANDLE) {
645 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
646 customPipelineLayout = VK_NULL_HANDLE;
649 std::string vertPath = vertexShaderPath.empty() ?
"sprite.vert.spv" : vertexShaderPath;
650 auto vertShaderCode = readShaderFile(vertPath);
653 VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
654 vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
655 vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
656 vertShaderStageInfo.module = vertShaderModule;
657 vertShaderStageInfo.pName =
"main";
659 VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
660 fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
661 fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
662 fragShaderStageInfo.module = fragmentShaderModule;
663 fragShaderStageInfo.pName =
"main";
665 VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
667 VkVertexInputBindingDescription bindingDescription{};
668 bindingDescription.binding = 0;
669 bindingDescription.stride =
sizeof(float) * 4;
670 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
672 std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
673 attributeDescriptions[0].binding = 0;
674 attributeDescriptions[0].location = 0;
675 attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
676 attributeDescriptions[0].offset = 0;
677 attributeDescriptions[1].binding = 0;
678 attributeDescriptions[1].location = 1;
679 attributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;
680 attributeDescriptions[1].offset =
sizeof(float) * 2;
682 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
683 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
684 vertexInputInfo.vertexBindingDescriptionCount = 1;
685 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
686 vertexInputInfo.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributeDescriptions.size());
687 vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
689 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
690 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
691 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
692 inputAssembly.primitiveRestartEnable = VK_FALSE;
694 std::vector<VkDynamicState> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
695 VkPipelineDynamicStateCreateInfo dynamicState{};
696 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
697 dynamicState.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
698 dynamicState.pDynamicStates = dynamicStates.data();
700 VkPipelineViewportStateCreateInfo viewportState{};
701 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
702 viewportState.viewportCount = 1;
703 viewportState.scissorCount = 1;
705 VkPipelineRasterizationStateCreateInfo rasterizer{};
706 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
707 rasterizer.depthClampEnable = VK_FALSE;
708 rasterizer.rasterizerDiscardEnable = VK_FALSE;
709 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
710 rasterizer.lineWidth = 1.0f;
711 rasterizer.cullMode = VK_CULL_MODE_NONE;
712 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
713 rasterizer.depthBiasEnable = VK_FALSE;
715 VkPipelineMultisampleStateCreateInfo multisampling{};
716 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
717 multisampling.sampleShadingEnable = VK_FALSE;
718 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
720 VkPipelineDepthStencilStateCreateInfo depthStencil{};
721 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
722 depthStencil.depthTestEnable = VK_FALSE;
723 depthStencil.depthWriteEnable = VK_FALSE;
725 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
726 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
727 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
728 colorBlendAttachment.blendEnable = VK_TRUE;
729 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
730 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
731 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
732 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
733 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
734 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
736 VkPipelineColorBlendStateCreateInfo colorBlending{};
737 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
738 colorBlending.logicOpEnable = VK_FALSE;
739 colorBlending.attachmentCount = 1;
740 colorBlending.pAttachments = &colorBlendAttachment;
742 VkPushConstantRange pushConstantRange{};
743 pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
744 pushConstantRange.offset = 0;
745 pushConstantRange.size =
sizeof(float) * 12;
747 VkDescriptorSetLayout layoutToUseForPipeline = extendedUBOEnabled ? extendedDescriptorSetLayout : descriptorSetLayout;
749 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
750 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
751 pipelineLayoutInfo.setLayoutCount = 1;
752 pipelineLayoutInfo.pSetLayouts = &layoutToUseForPipeline;
753 pipelineLayoutInfo.pushConstantRangeCount = 1;
754 pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
756 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo,
nullptr, &customPipelineLayout));
758 VkGraphicsPipelineCreateInfo pipelineInfo{};
759 VkPipelineRenderingCreateInfo renderingInfo{};
760 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
761 renderingInfo.viewMask = 0;
762 renderingInfo.colorAttachmentCount = 1;
763 renderingInfo.pColorAttachmentFormats = &colorAttachmentFormat;
764 if (depthAttachmentFormat != VK_FORMAT_UNDEFINED) {
765 renderingInfo.depthAttachmentFormat = depthAttachmentFormat;
768 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
769 pipelineInfo.pNext = &renderingInfo;
770 pipelineInfo.stageCount = 2;
771 pipelineInfo.pStages = shaderStages;
772 pipelineInfo.pVertexInputState = &vertexInputInfo;
773 pipelineInfo.pInputAssemblyState = &inputAssembly;
774 pipelineInfo.pViewportState = &viewportState;
775 pipelineInfo.pRasterizationState = &rasterizer;
776 pipelineInfo.pMultisampleState = &multisampling;
777 pipelineInfo.pDepthStencilState = &depthStencil;
778 pipelineInfo.pColorBlendState = &colorBlending;
779 pipelineInfo.pDynamicState = &dynamicState;
780 pipelineInfo.layout = customPipelineLayout;
781 pipelineInfo.renderPass = VK_NULL_HANDLE;
782 pipelineInfo.subpass = 0;
783 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
785 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo,
nullptr, &customPipeline));
787 vkDestroyShaderModule(device, vertShaderModule,
nullptr);
791 if (!hasCustomShader || fragmentShaderModule == VK_NULL_HANDLE)
793 createCustomPipeline();
794 std::cout <<
"mxvk: Pipeline rebuilt\n";
798 if (path == fragmentShaderPath && fragmentShaderModule != VK_NULL_HANDLE) {
802 if (customPipeline != VK_NULL_HANDLE) {
803 vkDestroyPipeline(device, customPipeline,
nullptr);
804 customPipeline = VK_NULL_HANDLE;
806 if (customPipelineLayout != VK_NULL_HANDLE) {
807 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
808 customPipelineLayout = VK_NULL_HANDLE;
810 if (fragmentShaderModule != VK_NULL_HANDLE) {
811 vkDestroyShaderModule(device, fragmentShaderModule,
nullptr);
812 fragmentShaderModule = VK_NULL_HANDLE;
815 fragmentShaderPath = path;
816 hasCustomShader =
false;
818 if (fragmentShaderPath.empty()) {
822 const auto shaderCode = readShaderFile(fragmentShaderPath);
824 hasCustomShader =
true;
826 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
827 createCustomPipeline();
832 if (!instancingEnabled || instanceVertPath.empty() || instanceFragPath.empty())
834 createInstancedPipeline(instanceVertPath, instanceFragPath);
837 void VK_Sprite::createQuadBuffer() {
838 if (quadBufferCreated)
841 SpriteVertex vertices[] = {
842 {{0.0f, 0.0f}, {0.0f, 0.0f}},
843 {{1.0f, 0.0f}, {1.0f, 0.0f}},
844 {{1.0f, 1.0f}, {1.0f, 1.0f}},
845 {{0.0f, 1.0f}, {0.0f, 1.0f}}};
846 uint16_t indices[] = {0, 1, 2, 0, 2, 3};
848 VkDeviceSize vertexSize =
sizeof(vertices);
849 createBuffer(vertexSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
850 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
851 quadVertexBuffer, quadVertexBufferMemory);
854 VK_CHECK_RESULT(vkMapMemory(device, quadVertexBufferMemory, 0, vertexSize, 0, &data));
855 memcpy(data, vertices, vertexSize);
856 vkUnmapMemory(device, quadVertexBufferMemory);
858 VkDeviceSize indexSize =
sizeof(indices);
859 createBuffer(indexSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
860 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
861 quadIndexBuffer, quadIndexBufferMemory);
863 VK_CHECK_RESULT(vkMapMemory(device, quadIndexBufferMemory, 0, indexSize, 0, &data));
864 memcpy(data, indices, indexSize);
865 vkUnmapMemory(device, quadIndexBufferMemory);
867 quadBufferCreated =
true;
877 std::cout << std::format(
"mxvk: Loaded PNG: {}\n", pngPath);
882 throw mxvk::Exception(
"VKSprite::loadSprite called with null surface");
884 if (spriteLoaded || spriteImage != VK_NULL_HANDLE || fragmentShaderModule != VK_NULL_HANDLE) {
885 destroySpriteResources();
887 SDL_Surface *rgbaSurface = convertToRGBA(
surface);
891 spriteWidth = rgbaSurface->w;
892 spriteHeight = rgbaSurface->h;
893 createSpriteTexture(rgbaSurface);
894 SDL_DestroySurface(rgbaSurface);
897 if (!fragmentShaderPath.empty()) {
898 auto shaderCode = readShaderFile(fragmentShaderPath);
900 hasCustomShader =
true;
901 this->fragmentShaderPath = fragmentShaderPath;
903 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
904 createCustomPipeline();
908 std::cout << std::format(
"mxvk: Loaded surface texture: {}x{}\n", spriteWidth, spriteHeight);
912 if (width <= 0 || height <= 0) {
913 throw mxvk::Exception(
"VKSprite::createEmptySprite invalid dimensions");
915 if (spriteLoaded || spriteImage != VK_NULL_HANDLE || fragmentShaderModule != VK_NULL_HANDLE) {
916 destroySpriteResources();
919 spriteHeight = height;
921 if (!vertexShaderPath.empty()) {
927 createCudaExportableImage(width, height, spriteImage, spriteImageMemory);
928 }
catch (
const std::exception &ex) {
929 std::cout << std::format(
"mxvk: CUDA exportable sprite image unavailable: {}; using standard Vulkan image\n", ex.what());
930 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
931 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
932 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
935 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
936 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
937 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
940 VkBuffer stagingBuffer = VK_NULL_HANDLE;
941 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
942 VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) * height * 4;
944 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
945 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
946 stagingBuffer, stagingMemory);
949 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
950 memset(data, 0, imageSize);
951 vkUnmapMemory(device, stagingMemory);
953 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
954 copyBufferToImage(stagingBuffer, spriteImage, width, height);
955 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
957 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
960 vkDestroyBuffer(device, stagingBuffer,
nullptr);
961 vkFreeMemory(device, stagingMemory,
nullptr);
963 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
966 createDescriptorPool();
968 createStagingResources(imageSize);
970 if (!fragmentShaderPath.empty()) {
971 auto shaderCode = readShaderFile(fragmentShaderPath);
973 hasCustomShader =
true;
974 this->fragmentShaderPath = fragmentShaderPath;
976 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
977 createCustomPipeline();
982 std::cout << std::format(
"mxvk: Created empty sprite: {}x{}\n", spriteWidth, spriteHeight);
987 throw mxvk::Exception(
"VKSprite::updateTexture called with null surface");
990 throw mxvk::Exception(
"VKSprite::updateTexture called before sprite was loaded");
992 SDL_Surface *rgbaSurface = convertToRGBA(
surface);
994 throw mxvk::Exception(
"Failed to convert surface to RGBA in updateTexture");
996 if (rgbaSurface->w == spriteWidth && rgbaSurface->h == spriteHeight) {
998 if (updateTextureCudaHost(rgbaSurface->pixels,
static_cast<uint32_t
>(rgbaSurface->w),
static_cast<uint32_t
>(rgbaSurface->h),
999 static_cast<uint32_t
>(rgbaSurface->pitch))) {
1000 SDL_DestroySurface(rgbaSurface);
1004 updateSpriteTexture(rgbaSurface->pixels, rgbaSurface->w, rgbaSurface->h);
1006 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
1007 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1010 destroyCudaInterop();
1012 destroyTextureDescriptorPools();
1013 if (spriteImageView != VK_NULL_HANDLE) {
1014 vkDestroyImageView(device, spriteImageView,
nullptr);
1015 spriteImageView = VK_NULL_HANDLE;
1017 if (spriteImage != VK_NULL_HANDLE) {
1018 vkDestroyImage(device, spriteImage,
nullptr);
1019 spriteImage = VK_NULL_HANDLE;
1021 if (spriteImageMemory != VK_NULL_HANDLE) {
1022 vkFreeMemory(device, spriteImageMemory,
nullptr);
1023 spriteImageMemory = VK_NULL_HANDLE;
1025 spriteWidth = rgbaSurface->w;
1026 spriteHeight = rgbaSurface->h;
1027 createSpriteTexture(rgbaSurface);
1028 createDescriptorPool();
1030 SDL_DestroySurface(rgbaSurface);
1035 throw mxvk::Exception(
"VKSprite::updateTexture called with null pixel data");
1037 if (!spriteLoaded) {
1038 throw mxvk::Exception(
"VKSprite::updateTexture called before sprite was loaded");
1040 if (width <= 0 || height <= 0) {
1043 int srcPitch = (pitch > 0) ? pitch : width * 4;
1044 if (width == spriteWidth && height == spriteHeight && srcPitch == width * 4) {
1046 if (updateTextureCudaHost(pixels,
static_cast<uint32_t
>(width),
static_cast<uint32_t
>(height),
static_cast<uint32_t
>(srcPitch))) {
1050 updateSpriteTexture(pixels, width, height);
1051 }
else if (width == spriteWidth && height == spriteHeight) {
1053 if (updateTextureCudaHost(pixels,
static_cast<uint32_t
>(width),
static_cast<uint32_t
>(height),
static_cast<uint32_t
>(srcPitch))) {
1057 std::vector<uint8_t> packed(width * height * 4);
1058 const uint8_t *src =
static_cast<const uint8_t *
>(pixels);
1059 for (
int row = 0; row < height; ++row) {
1060 memcpy(packed.data() + row * width * 4, src + row * srcPitch, width * 4);
1062 updateSpriteTexture(packed.data(), width, height);
1064 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
1065 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1068 destroyCudaInterop();
1070 destroyTextureDescriptorPools();
1071 if (spriteImageView != VK_NULL_HANDLE) {
1072 vkDestroyImageView(device, spriteImageView,
nullptr);
1073 spriteImageView = VK_NULL_HANDLE;
1075 if (spriteImage != VK_NULL_HANDLE) {
1076 vkDestroyImage(device, spriteImage,
nullptr);
1077 spriteImage = VK_NULL_HANDLE;
1079 if (spriteImageMemory != VK_NULL_HANDLE) {
1080 vkFreeMemory(device, spriteImageMemory,
nullptr);
1081 spriteImageMemory = VK_NULL_HANDLE;
1083 spriteWidth = width;
1084 spriteHeight = height;
1085 std::vector<uint8_t> packed;
1086 const void *texData = pixels;
1087 if (srcPitch != width * 4) {
1088 packed.resize(width * height * 4);
1089 const uint8_t *src =
static_cast<const uint8_t *
>(pixels);
1090 for (
int row = 0; row < height; ++row) {
1091 memcpy(packed.data() + row * width * 4, src + row * srcPitch, width * 4);
1093 texData = packed.data();
1096 SDL_Surface *tmpSurface = SDL_CreateSurfaceFrom(
1097 width, height, SDL_PIXELFORMAT_RGBA32,
1098 const_cast<void *
>(texData), width * 4);
1100 throw mxvk::Exception(
"VKSprite::updateTexture failed to create temp surface");
1102 createSpriteTexture(tmpSurface);
1103 SDL_DestroySurface(tmpSurface);
1104 createDescriptorPool();
1108 void VK_Sprite::updateSpriteTexture(
const void *pixels, uint32_t width, uint32_t height) {
1109 VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) * height * 4;
1111 createStagingResources(imageSize);
1112 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
1114 memcpy(persistentStagingMapped, pixels, imageSize);
1116 VkCommandBufferBeginInfo beginInfo{};
1117 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1118 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1120 VkImageMemoryBarrier barrier{};
1121 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1122 VkImageLayout oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1124 if (cudaImageLayout != VK_IMAGE_LAYOUT_UNDEFINED) {
1125 oldLayout = cudaImageLayout;
1128 barrier.oldLayout = oldLayout;
1129 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
1130 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1131 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1132 barrier.image = spriteImage;
1133 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1134 barrier.subresourceRange.baseMipLevel = 0;
1135 barrier.subresourceRange.levelCount = 1;
1136 barrier.subresourceRange.baseArrayLayer = 0;
1137 barrier.subresourceRange.layerCount = 1;
1138 barrier.srcAccessMask = (oldLayout == VK_IMAGE_LAYOUT_GENERAL) ? VK_ACCESS_MEMORY_WRITE_BIT : VK_ACCESS_SHADER_READ_BIT;
1139 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1140 const VkPipelineStageFlags srcStage = (oldLayout == VK_IMAGE_LAYOUT_GENERAL)
1141 ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT
1142 : VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1143 vkCmdPipelineBarrier(uploadCmdBuffer, srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT,
1144 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1146 VkBufferImageCopy region{};
1147 region.bufferOffset = 0;
1148 region.bufferRowLength = 0;
1149 region.bufferImageHeight = 0;
1150 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1151 region.imageSubresource.mipLevel = 0;
1152 region.imageSubresource.baseArrayLayer = 0;
1153 region.imageSubresource.layerCount = 1;
1154 region.imageOffset = {0, 0, 0};
1155 region.imageExtent = {width, height, 1};
1156 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer, spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
1158 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
1159 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1160 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1161 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1162 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
1163 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1167 VkSubmitInfo submitInfo{};
1168 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1169 submitInfo.commandBufferCount = 1;
1170 submitInfo.pCommandBuffers = &uploadCmdBuffer;
1171 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
1173 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1174 cudaImageNeedsShaderBarrier =
false;
1179 void VK_Sprite::destroyCudaInterop() {
1180 if (cudaInteropEnabled || cudaExternalMemory !=
nullptr || cudaMipmappedArray !=
nullptr) {
1181 std::cout <<
"mxvk: CUDA interop: destroying imported Vulkan texture resources\n";
1183 if (cudaMipmappedArray !=
nullptr) {
1184 cudaFreeMipmappedArray(cudaMipmappedArray);
1185 cudaMipmappedArray =
nullptr;
1186 cudaArray =
nullptr;
1188 if (cudaExternalMemory !=
nullptr) {
1189 cudaDestroyExternalMemory(cudaExternalMemory);
1190 cudaExternalMemory =
nullptr;
1192 cudaInteropEnabled =
false;
1193 cudaImageNeedsShaderBarrier =
false;
1194 cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1195 cudaExportMemorySize = 0;
1196 cudaUploadLogged =
false;
1197 cudaWriteTransitionLogged =
false;
1198 cudaSampleBarrierLogged =
false;
1201 void VK_Sprite::createCudaExportableImage(uint32_t width, uint32_t height, VkImage &image, VkDeviceMemory &imageMemory) {
1202 std::cout << std::format(
"mxvk: CUDA interop init: requesting exportable Vulkan image {}x{} RGBA8 OPAQUE_FD\n", width, height);
1203 if (image != VK_NULL_HANDLE) {
1204 vkDestroyImage(device, image,
nullptr);
1205 image = VK_NULL_HANDLE;
1207 if (imageMemory != VK_NULL_HANDLE) {
1208 vkFreeMemory(device, imageMemory,
nullptr);
1209 imageMemory = VK_NULL_HANDLE;
1212 VkExternalMemoryImageCreateInfo externalImageInfo{};
1213 externalImageInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
1214 externalImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
1216 VkImageCreateInfo imageInfo{};
1217 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1218 imageInfo.pNext = &externalImageInfo;
1219 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1220 imageInfo.extent.width = width;
1221 imageInfo.extent.height = height;
1222 imageInfo.extent.depth = 1;
1223 imageInfo.mipLevels = 1;
1224 imageInfo.arrayLayers = 1;
1225 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
1226 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
1227 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1228 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
1229 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1230 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1234 VkMemoryRequirements memRequirements{};
1235 vkGetImageMemoryRequirements(device, image, &memRequirements);
1237 VkExportMemoryAllocateInfo exportMemoryInfo{};
1238 exportMemoryInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
1239 exportMemoryInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
1241 VkMemoryAllocateInfo allocInfo{};
1242 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1243 allocInfo.pNext = &exportMemoryInfo;
1244 allocInfo.allocationSize = memRequirements.size;
1247 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
1248 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &imageMemory));
1250 cudaExportMemorySize = memRequirements.size;
1251 cudaInteropUnavailableLogged =
false;
1252 std::cout << std::format(
1253 "mxvk: CUDA interop init: exportable Vulkan image allocated (memorySize={} bytes, memoryType={})\n",
1254 static_cast<unsigned long long>(cudaExportMemorySize), allocInfo.memoryTypeIndex);
1256 if (imageMemory != VK_NULL_HANDLE) {
1257 vkFreeMemory(device, imageMemory,
nullptr);
1258 imageMemory = VK_NULL_HANDLE;
1260 if (image != VK_NULL_HANDLE) {
1261 vkDestroyImage(device, image,
nullptr);
1262 image = VK_NULL_HANDLE;
1264 cudaExportMemorySize = 0;
1269 bool VK_Sprite::ensureCudaInterop() {
1270 if (cudaInteropEnabled) {
1273 if (spriteImage == VK_NULL_HANDLE || spriteImageMemory == VK_NULL_HANDLE || cudaExportMemorySize == 0) {
1274 if (!cudaInteropUnavailableLogged) {
1275 std::cout <<
"mxvk: CUDA interop init: sprite image is not exportable; using CPU/pinned fallback\n";
1276 cudaInteropUnavailableLogged =
true;
1280 if (vkGetMemoryFdKHR ==
nullptr) {
1281 if (!cudaInteropUnavailableLogged) {
1282 std::cout <<
"mxvk: CUDA interop init: vkGetMemoryFdKHR was not loaded; using CPU/pinned fallback\n";
1283 cudaInteropUnavailableLogged =
true;
1288 VkMemoryGetFdInfoKHR fdInfo{};
1289 fdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
1290 fdInfo.memory = spriteImageMemory;
1291 fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
1294 const VkResult fdResult = vkGetMemoryFdKHR(device, &fdInfo, &memoryFd);
1295 if (fdResult != VK_SUCCESS) {
1296 if (!cudaInteropUnavailableLogged) {
1297 std::cout << std::format(
"mxvk: CUDA interop init: vkGetMemoryFdKHR failed ({})\n",
static_cast<int>(fdResult));
1298 cudaInteropUnavailableLogged =
true;
1302 std::cout << std::format(
"mxvk: CUDA interop init: exported Vulkan image memory fd={}\n", memoryFd);
1304 cudaExternalMemoryHandleDesc externalMemoryDesc{};
1305 externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueFd;
1306 externalMemoryDesc.handle.fd = memoryFd;
1307 externalMemoryDesc.size = cudaExportMemorySize;
1309 cudaError_t cudaResult = cudaImportExternalMemory(&cudaExternalMemory, &externalMemoryDesc);
1310 if (cudaResult != cudaSuccess) {
1312 if (!cudaInteropUnavailableLogged) {
1313 std::cout << std::format(
"mxvk: CUDA interop init: cudaImportExternalMemory failed: {}\n",
1314 cudaGetErrorString(cudaResult));
1315 cudaInteropUnavailableLogged =
true;
1317 cudaExternalMemory =
nullptr;
1320 std::cout << std::format(
"mxvk: CUDA interop init: imported external memory into CUDA ({} bytes)\n",
1321 static_cast<unsigned long long>(cudaExportMemorySize));
1323 cudaExternalMemoryMipmappedArrayDesc arrayDesc{};
1324 arrayDesc.offset = 0;
1325 arrayDesc.formatDesc = cudaCreateChannelDesc<uchar4>();
1326 arrayDesc.extent = make_cudaExtent(
static_cast<size_t>(spriteWidth),
static_cast<size_t>(spriteHeight), 0);
1327 arrayDesc.flags = cudaArrayColorAttachment;
1328 arrayDesc.numLevels = 1;
1330 cudaResult = cudaExternalMemoryGetMappedMipmappedArray(&cudaMipmappedArray, cudaExternalMemory, &arrayDesc);
1331 if (cudaResult != cudaSuccess) {
1332 if (!cudaInteropUnavailableLogged) {
1333 std::cout << std::format(
"mxvk: CUDA interop init: cudaExternalMemoryGetMappedMipmappedArray failed: {}\n",
1334 cudaGetErrorString(cudaResult));
1335 cudaInteropUnavailableLogged =
true;
1337 destroyCudaInterop();
1340 std::cout << std::format(
"mxvk: CUDA interop init: mapped CUDA mipmapped array {}x{} uchar4\n", spriteWidth, spriteHeight);
1342 cudaResult = cudaGetMipmappedArrayLevel(&cudaArray, cudaMipmappedArray, 0);
1343 if (cudaResult != cudaSuccess) {
1344 if (!cudaInteropUnavailableLogged) {
1345 std::cout << std::format(
"mxvk: CUDA interop init: cudaGetMipmappedArrayLevel failed: {}\n",
1346 cudaGetErrorString(cudaResult));
1347 cudaInteropUnavailableLogged =
true;
1349 destroyCudaInterop();
1353 cudaInteropEnabled =
true;
1354 std::cout <<
"mxvk: CUDA interop init: direct CUDA-to-Vulkan texture upload is ready\n";
1358 bool VK_Sprite::transitionCudaImageForWrite() {
1359 if (cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) {
1363 const VkImageLayout oldLayout = (cudaImageLayout == VK_IMAGE_LAYOUT_UNDEFINED)
1364 ? VK_IMAGE_LAYOUT_UNDEFINED
1366 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1368 VkImageMemoryBarrier barrier{};
1369 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1370 barrier.oldLayout = oldLayout;
1371 barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
1372 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1373 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1374 barrier.image = spriteImage;
1375 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1376 barrier.subresourceRange.baseMipLevel = 0;
1377 barrier.subresourceRange.levelCount = 1;
1378 barrier.subresourceRange.baseArrayLayer = 0;
1379 barrier.subresourceRange.layerCount = 1;
1380 barrier.srcAccessMask = (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) ? VK_ACCESS_SHADER_READ_BIT : 0;
1381 barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1383 const VkPipelineStageFlags srcStage = (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
1384 ? VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
1385 : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1386 vkCmdPipelineBarrier(commandBuffer, srcStage, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
1387 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1388 endSingleTimeCommands(commandBuffer);
1390 cudaImageLayout = VK_IMAGE_LAYOUT_GENERAL;
1391 if (!cudaWriteTransitionLogged) {
1392 std::cout <<
"mxvk: CUDA interop sync: Vulkan image transitions to GENERAL before CUDA writes\n";
1393 cudaWriteTransitionLogged =
true;
1398 bool VK_Sprite::transitionCudaImageForShaderRead() {
1399 if (cudaImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && !cudaImageNeedsShaderBarrier) {
1403 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1404 VkImageMemoryBarrier barrier{};
1405 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1406 barrier.oldLayout = cudaImageLayout;
1407 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1408 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1409 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1410 barrier.image = spriteImage;
1411 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1412 barrier.subresourceRange.baseMipLevel = 0;
1413 barrier.subresourceRange.levelCount = 1;
1414 barrier.subresourceRange.baseArrayLayer = 0;
1415 barrier.subresourceRange.layerCount = 1;
1416 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1417 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1419 vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
1420 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1421 endSingleTimeCommands(commandBuffer);
1423 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1424 cudaImageNeedsShaderBarrier =
false;
1425 if (!cudaSampleBarrierLogged) {
1426 std::cout <<
"mxvk: CUDA interop sync: Vulkan transitions GENERAL -> SHADER_READ_ONLY before sampling\n";
1427 cudaSampleBarrierLogged =
true;
1432 void VK_Sprite::recordCudaReadyBarrier(VkCommandBuffer cmdBuffer) {
1433 if (!cudaImageNeedsShaderBarrier) {
1438 transitionCudaImageForShaderRead();
1441 bool VK_Sprite::updateTextureCuda(
const cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream) {
1442 if (!spriteLoaded) {
1445 if (rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols <= 0 || rgba.rows <= 0) {
1448 if (rgba.cols != spriteWidth || rgba.rows != spriteHeight || spriteImage == VK_NULL_HANDLE ||
1449 spriteImageMemory == VK_NULL_HANDLE || spriteImageView == VK_NULL_HANDLE || cudaExportMemorySize == 0) {
1450 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
1451 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1453 vkDeviceWaitIdle(device);
1454 destroyCudaInterop();
1455 destroyTextureDescriptorPools();
1456 if (spriteImageView != VK_NULL_HANDLE) {
1457 vkDestroyImageView(device, spriteImageView,
nullptr);
1458 spriteImageView = VK_NULL_HANDLE;
1460 if (spriteImage != VK_NULL_HANDLE) {
1461 vkDestroyImage(device, spriteImage,
nullptr);
1462 spriteImage = VK_NULL_HANDLE;
1464 if (spriteImageMemory != VK_NULL_HANDLE) {
1465 vkFreeMemory(device, spriteImageMemory,
nullptr);
1466 spriteImageMemory = VK_NULL_HANDLE;
1469 spriteWidth = rgba.cols;
1470 spriteHeight = rgba.rows;
1472 createCudaExportableImage(
static_cast<uint32_t
>(spriteWidth),
static_cast<uint32_t
>(spriteHeight),
1473 spriteImage, spriteImageMemory);
1474 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
1475 }
catch (
const std::exception &ex) {
1476 if (!cudaInteropUnavailableLogged) {
1477 std::cout << std::format(
"mxvk: CUDA exportable sprite resize unavailable: {}; using CPU/pinned fallback\n", ex.what());
1478 cudaInteropUnavailableLogged =
true;
1483 createDescriptorPool();
1489 if (!ensureCudaInterop() || !transitionCudaImageForWrite()) {
1493 cudaStream_t cudaStream = cuda_stream_handle(stream);
1494 if (!cudaUploadLogged) {
1495 std::cout << std::format(
"mxvk: CUDA interop upload: copying {}x{} RGBA GpuMat to Vulkan image array (pitch={} bytes)\n",
1496 rgba.cols, rgba.rows,
static_cast<unsigned long long>(rgba.step));
1497 cudaUploadLogged =
true;
1499 cudaError_t cudaResult = cudaMemcpy2DToArrayAsync(
1500 cudaArray, 0, 0, rgba.ptr(), rgba.step,
1501 static_cast<size_t>(rgba.cols) * 4,
static_cast<size_t>(rgba.rows),
1502 cudaMemcpyDeviceToDevice, cudaStream);
1503 if (cudaResult != cudaSuccess) {
1504 std::cout << std::format(
"mxvk: CUDA interop texture copy failed: {}\n", cudaGetErrorString(cudaResult));
1508 cudaResult = cudaStreamSynchronize(cudaStream);
1509 if (cudaResult != cudaSuccess) {
1510 std::cout << std::format(
"mxvk: CUDA interop texture sync failed: {}\n", cudaGetErrorString(cudaResult));
1514 cudaImageNeedsShaderBarrier =
true;
1515 return transitionCudaImageForShaderRead();
1518 bool VK_Sprite::updateTextureCudaHost(
const void *pixels, uint32_t width, uint32_t height, uint32_t pitch) {
1519 if (pixels ==
nullptr || width == 0 || height == 0) {
1522 const uint32_t rowBytes = width * 4U;
1523 if (pitch < rowBytes ||
static_cast<int>(width) != spriteWidth ||
static_cast<int>(height) != spriteHeight) {
1526 if (!ensureCudaInterop() || !transitionCudaImageForWrite()) {
1530 if (!cudaUploadLogged) {
1531 std::cout << std::format(
1532 "mxvk: CUDA interop upload: copying {}x{} host RGBA pixels to Vulkan image array (pitch={} bytes)\n",
1533 width, height, pitch);
1534 cudaUploadLogged =
true;
1537 const cudaError_t copyResult = cudaMemcpy2DToArray(
1538 cudaArray, 0, 0, pixels, pitch,
1539 static_cast<size_t>(rowBytes),
static_cast<size_t>(height),
1540 cudaMemcpyHostToDevice);
1541 if (copyResult != cudaSuccess) {
1542 std::cout << std::format(
"mxvk: CUDA interop host texture copy failed: {}\n", cudaGetErrorString(copyResult));
1546 cudaImageNeedsShaderBarrier =
true;
1547 return transitionCudaImageForShaderRead();
1551 void VK_Sprite::createSpriteTexture(SDL_Surface *surface) {
1554 createCudaExportableImage(surface->w, surface->h, spriteImage, spriteImageMemory);
1555 }
catch (
const std::exception &ex) {
1556 std::cout << std::format(
"mxvk: CUDA exportable sprite image unavailable: {}; using standard Vulkan image\n", ex.what());
1557 createImage(surface->w, surface->h, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
1558 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1559 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
1562 createImage(surface->w, surface->h, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
1563 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1564 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
1568 if (updateTextureCudaHost(surface->pixels,
static_cast<uint32_t
>(surface->w),
static_cast<uint32_t
>(surface->h),
1569 static_cast<uint32_t
>(surface->pitch))) {
1570 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
1575 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1576 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
1577 VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(surface->w) * surface->h * 4;
1579 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
1580 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1581 stagingBuffer, stagingMemory);
1584 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
1585 const int rowBytes = surface->w * 4;
1586 if (surface->pitch == rowBytes) {
1587 memcpy(data, surface->pixels, imageSize);
1589 const auto *src =
static_cast<const uint8_t *
>(surface->pixels);
1590 auto *dst =
static_cast<uint8_t *
>(data);
1591 for (
int y = 0; y < surface->h; ++y)
1592 memcpy(dst + y * rowBytes, src + y * surface->pitch, rowBytes);
1594 vkUnmapMemory(device, stagingMemory);
1597 const VkImageLayout uploadOldLayout = (cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL)
1598 ? VK_IMAGE_LAYOUT_GENERAL
1599 : VK_IMAGE_LAYOUT_UNDEFINED;
1601 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1603 transitionImageLayout(spriteImage, uploadOldLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1604 copyBufferToImage(stagingBuffer, spriteImage, surface->w, surface->h);
1605 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1607 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1610 vkDestroyBuffer(device, stagingBuffer,
nullptr);
1611 vkFreeMemory(device, stagingMemory,
nullptr);
1613 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
1616 void VK_Sprite::createSampler() {
1621 VkSamplerCreateInfo samplerInfo{};
1622 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1623 samplerInfo.magFilter = textureFilter;
1624 samplerInfo.minFilter = textureFilter;
1625 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1626 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1627 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1628 samplerInfo.anisotropyEnable = VK_FALSE;
1629 samplerInfo.borderColor = VK_BORDER_COLOR_INT_TRANSPARENT_BLACK;
1630 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1631 samplerInfo.compareEnable = VK_FALSE;
1632 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1633 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
1634 samplerInfo.minLod = 0.0f;
1635 samplerInfo.maxLod = 0.0f;
1636 samplerInfo.mipLodBias = 0.0f;
1646 drawSpriteRect(x, y,
static_cast<int>(spriteWidth * scaleX),
static_cast<int>(spriteHeight * scaleY));
1650 if (!spriteLoaded) {
1651 throw mxvk::Exception(
"VKSprite::drawSprite called before sprite was loaded");
1654 drawQueue.push_back({
static_cast<float>(x),
static_cast<float>(y),
1655 static_cast<float>(
static_cast<int>(spriteWidth * scaleX)),
1656 static_cast<float>(
static_cast<int>(spriteHeight * scaleY)),
1657 rotation, shaderParams});
1661 if (!spriteLoaded) {
1662 throw mxvk::Exception(
"VKSprite::drawSpriteRect called before sprite was loaded");
1665 drawQueue.push_back({
static_cast<float>(x),
static_cast<float>(y),
1666 static_cast<float>(w),
static_cast<float>(h), 0.0f, shaderParams});
1670 shaderParams = glm::vec4(p1, p2, p3, p4);
1674 if (image_view == VK_NULL_HANDLE || width <= 0 || height <= 0) {
1675 throw mxvk::Exception(
"VKSprite::setExternalTexture received an invalid image view");
1677 if (externalTexture && spriteImageView == image_view) {
1678 spriteWidth = width;
1679 spriteHeight = height;
1680 spriteLoaded =
true;
1683 auto cached_descriptor = externalDescriptorSets.find(image_view);
1684 descriptorSet = (cached_descriptor != externalDescriptorSets.end()) ? cached_descriptor->second : VK_NULL_HANDLE;
1685 descriptorSetPool = VK_NULL_HANDLE;
1686 if (!externalTexture) {
1687 destroyTextureDescriptorPools();
1688 }
else if (extendedDescriptorPool != VK_NULL_HANDLE) {
1689 vkDeviceWaitIdle(device);
1690 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
1691 extendedDescriptorPool = VK_NULL_HANDLE;
1692 extendedDescriptorSet = VK_NULL_HANDLE;
1694 if (!externalTexture && spriteImageView != VK_NULL_HANDLE) {
1695 vkDestroyImageView(device, spriteImageView,
nullptr);
1697 if (!externalTexture && spriteImage != VK_NULL_HANDLE) {
1698 vkDestroyImage(device, spriteImage,
nullptr);
1700 if (!externalTexture && spriteImageMemory != VK_NULL_HANDLE) {
1701 vkFreeMemory(device, spriteImageMemory,
nullptr);
1703 spriteImageView = image_view;
1704 spriteImage = VK_NULL_HANDLE;
1705 spriteImageMemory = VK_NULL_HANDLE;
1706 externalTexture =
true;
1707 spriteWidth = width;
1708 spriteHeight = height;
1709 spriteLoaded =
true;
1713 if (!externalTexture && externalDescriptorSets.empty()) {
1716 destroyTextureDescriptorPools();
1721 recordCudaReadyBarrier(cmdBuffer);
1726 uint32_t screenWidth, uint32_t screenHeight) {
1727 if (drawQueue.empty() || !spriteLoaded || !quadBufferCreated) {
1730 if (descriptorSet == VK_NULL_HANDLE) {
1731 descriptorSet = createDescriptorSet(spriteImageView);
1732 if (externalTexture) {
1733 externalDescriptorSets[spriteImageView] = descriptorSet;
1737 if (instancingEnabled && instancedPipeline != VK_NULL_HANDLE && instanceBuffer != VK_NULL_HANDLE) {
1738 uint32_t instanceCount =
static_cast<uint32_t
>(drawQueue.size());
1740 if (instanceCount > instanceBufferCapacity) {
1741 ensureInstanceBuffer(instanceCount * 2);
1744 SpriteInstanceData *dst =
static_cast<SpriteInstanceData *
>(instanceBufferMapped);
1745 for (uint32_t i = 0; i < instanceCount; ++i) {
1746 const auto &cmd = drawQueue[i];
1747 dst[i].posX = cmd.x;
1748 dst[i].posY = cmd.y;
1749 dst[i].sizeW = cmd.w;
1750 dst[i].sizeH = cmd.h;
1751 dst[i].params[0] = cmd.params.x;
1752 dst[i].params[1] = cmd.params.y;
1753 dst[i].params[2] = cmd.params.z;
1754 dst[i].params[3] = cmd.params.w;
1757 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, instancedPipeline);
1758 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, instancedPipelineLayout,
1759 0, 1, &descriptorSet, 0,
nullptr);
1761 VkBuffer buffers[] = {quadVertexBuffer, instanceBuffer};
1762 VkDeviceSize bufOffsets[] = {0, 0};
1763 vkCmdBindVertexBuffers(cmdBuffer, 0, 2, buffers, bufOffsets);
1764 vkCmdBindIndexBuffer(cmdBuffer, quadIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
1766 float screenSize[2] = {
static_cast<float>(screenWidth),
static_cast<float>(screenHeight)};
1767 vkCmdPushConstants(cmdBuffer, instancedPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT,
1768 0,
sizeof(screenSize), screenSize);
1770 vkCmdDrawIndexed(cmdBuffer, 6, instanceCount, 0, 0, 0);
1774 VkPipelineLayout layoutToUse = (customPipeline != VK_NULL_HANDLE) ? customPipelineLayout : pipelineLayout;
1775 if (customPipeline != VK_NULL_HANDLE) {
1776 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, customPipeline);
1780 if (extendedUBOEnabled && customPipeline != VK_NULL_HANDLE) {
1781 updateExtendedUBO();
1782 if (extendedDescriptorSet == VK_NULL_HANDLE) {
1783 createExtendedDescriptorSet();
1785 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layoutToUse,
1786 0, 1, &extendedDescriptorSet, 0,
nullptr);
1788 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layoutToUse,
1789 0, 1, &descriptorSet, 0,
nullptr);
1792 VkBuffer vertexBuffers[] = {quadVertexBuffer};
1793 VkDeviceSize offsets[] = {0};
1794 vkCmdBindVertexBuffers(cmdBuffer, 0, 1, vertexBuffers, offsets);
1795 vkCmdBindIndexBuffer(cmdBuffer, quadIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
1797 for (
const auto &cmd : drawQueue) {
1798 struct SpritePushConstants {
1809 static_cast<float>(screenWidth),
static_cast<float>(screenHeight), cmd.x, cmd.y, cmd.w, cmd.h, effectsEnabled ? 1.0f : 0.0f, cmd.rotation, {cmd.params.x, cmd.params.y, cmd.params.z, cmd.params.w}};
1811 vkCmdPushConstants(cmdBuffer, layoutToUse, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
1812 0,
sizeof(SpritePushConstants), &pc);
1814 vkCmdDrawIndexed(cmdBuffer, 6, 1, 0, 0, 0);
1822 void VK_Sprite::createDescriptorPool() {
1823 VkDescriptorPoolSize poolSize{};
1824 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1825 poolSize.descriptorCount = nextDescriptorPoolSets;
1827 VkDescriptorPoolCreateInfo poolInfo{};
1828 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1829 poolInfo.poolSizeCount = 1;
1830 poolInfo.pPoolSizes = &poolSize;
1831 poolInfo.maxSets = nextDescriptorPoolSets;
1832 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
1834 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo,
nullptr, &descriptorPool));
1835 descriptorPools.push_back(descriptorPool);
1836 if (nextDescriptorPoolSets <= (std::numeric_limits<uint32_t>::max() / 2U)) {
1837 nextDescriptorPoolSets *= 2U;
1841 void VK_Sprite::destroyDescriptorPools() {
1842 for (VkDescriptorPool pool : descriptorPools) {
1843 if (pool != VK_NULL_HANDLE) {
1844 vkDestroyDescriptorPool(device, pool,
nullptr);
1847 descriptorPools.clear();
1848 descriptorPool = VK_NULL_HANDLE;
1849 descriptorSetPool = VK_NULL_HANDLE;
1850 descriptorSet = VK_NULL_HANDLE;
1851 externalDescriptorSets.clear();
1852 nextDescriptorPoolSets = 16;
1855 void VK_Sprite::destroyTextureDescriptorPools() {
1856 if (!descriptorPools.empty() || extendedDescriptorPool != VK_NULL_HANDLE) {
1857 vkDeviceWaitIdle(device);
1859 destroyDescriptorPools();
1860 if (extendedDescriptorPool != VK_NULL_HANDLE) {
1861 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
1862 extendedDescriptorPool = VK_NULL_HANDLE;
1863 extendedDescriptorSet = VK_NULL_HANDLE;
1867 VkDescriptorSet VK_Sprite::createDescriptorSet(VkImageView imageView) {
1868 if (descriptorSetLayout == VK_NULL_HANDLE) {
1869 throw mxvk::Exception(
"VKSprite::createDescriptorSet called before setDescriptorSetLayout");
1872 if (descriptorPool == VK_NULL_HANDLE) {
1873 createDescriptorPool();
1876 VkDescriptorSetAllocateInfo allocInfo{};
1877 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1878 allocInfo.descriptorPool = descriptorPool;
1879 allocInfo.descriptorSetCount = 1;
1880 allocInfo.pSetLayouts = &descriptorSetLayout;
1882 VkDescriptorSet descSet = VK_NULL_HANDLE;
1883 VkResult allocateResult = vkAllocateDescriptorSets(device, &allocInfo, &descSet);
1884 if (allocateResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocateResult == VK_ERROR_FRAGMENTED_POOL) {
1885 createDescriptorPool();
1886 allocInfo.descriptorPool = descriptorPool;
1887 allocateResult = vkAllocateDescriptorSets(device, &allocInfo, &descSet);
1889 if (allocateResult != VK_SUCCESS) {
1890 throw mxvk::Exception(std::format(
"Fatal : VkResult is \"{}\" in {} at line {}",
static_cast<int>(allocateResult), __FILE__, __LINE__));
1892 descriptorSetPool = allocInfo.descriptorPool;
1894 VkDescriptorImageInfo imageInfo{};
1895 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1896 imageInfo.imageView = imageView;
1899 VkWriteDescriptorSet descriptorWrite{};
1900 descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1901 descriptorWrite.dstSet = descSet;
1902 descriptorWrite.dstBinding = 0;
1903 descriptorWrite.dstArrayElement = 0;
1904 descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1905 descriptorWrite.descriptorCount = 1;
1906 descriptorWrite.pImageInfo = &imageInfo;
1908 vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0,
nullptr);
1913 void VK_Sprite::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
1914 VkMemoryPropertyFlags properties, VkBuffer &buffer,
1915 VkDeviceMemory &bufferMemory) {
1916 VkBuffer newBuffer = VK_NULL_HANDLE;
1917 VkDeviceMemory newMemory = VK_NULL_HANDLE;
1919 VkBufferCreateInfo bufferInfo{};
1920 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1921 bufferInfo.size = size;
1922 bufferInfo.usage = usage;
1923 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1926 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo,
nullptr, &newBuffer));
1928 VkMemoryRequirements memRequirements;
1929 vkGetBufferMemoryRequirements(device, newBuffer, &memRequirements);
1931 VkMemoryAllocateInfo allocInfo{};
1932 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1933 allocInfo.allocationSize = memRequirements.size;
1934 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
1935 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
1938 if (newBuffer != VK_NULL_HANDLE) {
1939 vkDestroyBuffer(device, newBuffer,
nullptr);
1941 if (newMemory != VK_NULL_HANDLE) {
1942 vkFreeMemory(device, newMemory,
nullptr);
1947 if (buffer != VK_NULL_HANDLE) {
1948 vkDestroyBuffer(device, buffer,
nullptr);
1950 if (bufferMemory != VK_NULL_HANDLE) {
1951 vkFreeMemory(device, bufferMemory,
nullptr);
1954 bufferMemory = newMemory;
1957 uint32_t VK_Sprite::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
1958 VkPhysicalDeviceMemoryProperties memProperties;
1959 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
1961 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
1962 if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
1967 throw mxvk::Exception(
"Failed to find suitable memory type!");
1970 void VK_Sprite::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) {
1971 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1973 VkImageMemoryBarrier barrier{};
1974 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1975 barrier.oldLayout = oldLayout;
1976 barrier.newLayout = newLayout;
1977 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1978 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1979 barrier.image = image;
1980 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1981 barrier.subresourceRange.baseMipLevel = 0;
1982 barrier.subresourceRange.levelCount = 1;
1983 barrier.subresourceRange.baseArrayLayer = 0;
1984 barrier.subresourceRange.layerCount = 1;
1986 VkPipelineStageFlags sourceStage;
1987 VkPipelineStageFlags destinationStage;
1989 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1990 barrier.srcAccessMask = 0;
1991 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1992 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1993 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1994 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
1995 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1996 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1997 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1998 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1999 }
else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
2000 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
2001 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2002 sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
2003 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2004 }
else if (oldLayout == VK_IMAGE_LAYOUT_GENERAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
2005 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
2006 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2007 sourceStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
2008 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2010 throw std::invalid_argument(
"unsupported layout transition!");
2013 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
2014 endSingleTimeCommands(commandBuffer);
2017 void VK_Sprite::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) {
2018 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2020 VkBufferImageCopy region{};
2021 region.bufferOffset = 0;
2022 region.bufferRowLength = 0;
2023 region.bufferImageHeight = 0;
2024 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2025 region.imageSubresource.mipLevel = 0;
2026 region.imageSubresource.baseArrayLayer = 0;
2027 region.imageSubresource.layerCount = 1;
2028 region.imageOffset = {0, 0, 0};
2029 region.imageExtent = {width, height, 1};
2031 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
2033 endSingleTimeCommands(commandBuffer);
2036 VkCommandBuffer VK_Sprite::beginSingleTimeCommands() {
2037 VkCommandBufferAllocateInfo allocInfo{};
2038 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
2039 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
2040 allocInfo.commandPool = commandPool;
2041 allocInfo.commandBufferCount = 1;
2043 VkCommandBuffer commandBuffer;
2044 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
2046 VkCommandBufferBeginInfo beginInfo{};
2047 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
2048 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
2052 return commandBuffer;
2055 void VK_Sprite::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
2058 VkSubmitInfo submitInfo{};
2059 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2060 submitInfo.commandBufferCount = 1;
2061 submitInfo.pCommandBuffers = &commandBuffer;
2063 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE));
2066 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
2069 void VK_Sprite::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling,
2070 VkImageUsageFlags usage, VkMemoryPropertyFlags properties,
2071 VkImage &image, VkDeviceMemory &imageMemory) {
2072 VkImage newImage = VK_NULL_HANDLE;
2073 VkDeviceMemory newMemory = VK_NULL_HANDLE;
2075 VkImageCreateInfo imageInfo{};
2076 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2077 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2078 imageInfo.extent.width = width;
2079 imageInfo.extent.height = height;
2080 imageInfo.extent.depth = 1;
2081 imageInfo.mipLevels = 1;
2082 imageInfo.arrayLayers = 1;
2083 imageInfo.format = format;
2084 imageInfo.tiling = tiling;
2085 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2086 imageInfo.usage = usage;
2087 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2088 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2091 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo,
nullptr, &newImage));
2093 VkMemoryRequirements memRequirements;
2094 vkGetImageMemoryRequirements(device, newImage, &memRequirements);
2096 VkMemoryAllocateInfo allocInfo{};
2097 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
2098 allocInfo.allocationSize = memRequirements.size;
2099 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
2100 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
2103 if (newImage != VK_NULL_HANDLE) {
2104 vkDestroyImage(device, newImage,
nullptr);
2106 if (newMemory != VK_NULL_HANDLE) {
2107 vkFreeMemory(device, newMemory,
nullptr);
2112 if (image != VK_NULL_HANDLE) {
2113 vkDestroyImage(device, image,
nullptr);
2115 if (imageMemory != VK_NULL_HANDLE) {
2116 vkFreeMemory(device, imageMemory,
nullptr);
2119 imageMemory = newMemory;
2122 VkImageView VK_Sprite::createImageView(VkImage image, VkFormat format) {
2123 VkImageViewCreateInfo viewInfo{};
2124 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
2125 viewInfo.image = image;
2126 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
2127 viewInfo.format = format;
2128 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2129 viewInfo.subresourceRange.baseMipLevel = 0;
2130 viewInfo.subresourceRange.levelCount = 1;
2131 viewInfo.subresourceRange.baseArrayLayer = 0;
2132 viewInfo.subresourceRange.layerCount = 1;
2134 VkImageView imageView;
2135 VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo,
nullptr, &imageView));
2139 SDL_Surface *VK_Sprite::convertToRGBA(SDL_Surface *surface) {
2141 SDL_Surface *converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
2145 std::vector<char> VK_Sprite::readShaderFile(
const std::string &filename) {
2146 std::vector<std::filesystem::path> candidates{};
2147 const std::filesystem::path requested(filename);
2149 if (requested.is_absolute() || requested.has_parent_path()) {
2150 candidates.push_back(requested);
2152 if (
const char *basePath = SDL_GetBasePath(); basePath !=
nullptr) {
2153 const std::filesystem::path executableDir(basePath);
2154 candidates.push_back(executableDir /
"data" / requested);
2155 candidates.push_back(executableDir / requested);
2157 candidates.push_back(std::filesystem::path(
"data") / requested);
2158 candidates.push_back(requested);
2162 for (
const std::filesystem::path &candidate : candidates) {
2163 file.open(candidate, std::ios::ate | std::ios::binary);
2164 if (file.is_open()) {
2170 if (!file.is_open()) {
2171 throw mxvk::Exception(
"Failed to open shader file: " + filename);
2174 size_t fileSize =
static_cast<size_t>(file.tellg());
2175 std::vector<char> buffer(fileSize);
2177 file.read(buffer.data(), fileSize);
void setUniform3(float x, float y, float z, float w)
Upload user uniform 3 to the extended UBO.
void enableInstancing(uint32_t maxInstances, const std::string &instanceVertShaderPath, const std::string &instanceFragShaderPath)
Enable GPU instancing for this sprite type.
void renderSprites(VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout, uint32_t screenWidth, uint32_t screenHeight)
Record all queued draw commands into the given command buffer.
void setVertexShaderPath(const std::string &path)
Override the vertex shader path (used when rebuilding the pipeline).
void releaseUploadResources()
Release upload/staging resources tied to the current command pool.
~VK_Sprite()
Destructor — frees all Vulkan resources.
void setUniform1(float x, float y, float z, float w)
Upload user uniform 1 to the extended UBO.
void setUniform0(float x, float y, float z, float w)
Upload user uniform 0 to the extended UBO.
void setCommandPool(VkCommandPool pool)
Rebind the command pool used for upload/staging operations.
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.
void enableExtendedUBO()
Allocate and initialise the extended uniform buffer object.
void prepareForRendering(VkCommandBuffer cmdBuffer)
Record texture barriers that must happen before dynamic rendering begins.
void updateTexture(SDL_Surface *surface)
Replace the sprite texture from an SDL_Surface.
void rebuildInstancedPipeline()
Destroy and recreate the instanced graphics pipeline.
void loadSprite(const std::string &pngPath, const std::string &fragmentShaderPath="")
Load sprite texture from a PNG file.
void clearExternalTextureDescriptors()
void createEmptySprite(int width, int height, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a blank (un-initialised) sprite texture.
void drawSprite(int x, int y)
Queue a draw at the given pixel position.
void setMouseState(float mx, float my, float pressed, float reserved=0.0f)
Upload mouse state to the extended UBO.
void setExternalTexture(VkImageView image_view, int width, int height)
void setTextureFilter(VkFilter filter)
Select the hardware filter used when scaling this sprite.
void setFragmentShaderPath(const std::string &path)
Replace the fragment shader path and rebuild the custom pipeline.
void clearQueue()
Discard all pending draw commands without rendering.
void rebuildPipeline()
Destroy and recreate the custom graphics pipeline.
VK_Sprite(VkDevice device, VkPhysicalDevice physicalDevice, VkQueue graphicsQueue, VkCommandPool commandPool)
Construct and record Vulkan context handles.
void setUniform2(float x, float y, float z, float w)
Upload user uniform 2 to the extended UBO.
void drawSpriteRect(int x, int y, int w, int h)
Queue a draw into an explicit destination rectangle.
VkSampler spriteSampler
Texture sampler.
Small compatibility wrappers around OpenCV CUDA APIs.
PNG image loading and saving utilities via SDL3.
Vulkan 2-D sprite renderer with optional custom shaders and instancing.
#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.