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 std::cout <<
"vk: freeing sprite image memory\n";
93 vkFreeMemory(device, spriteImageMemory,
nullptr);
96 if (fragmentShaderModule != VK_NULL_HANDLE) {
97 vkDestroyShaderModule(device, fragmentShaderModule,
nullptr);
100 if (customPipeline != VK_NULL_HANDLE) {
101 std::cout <<
"vk: destroying sprite custom pipeline\n";
102 vkDestroyPipeline(device, customPipeline,
nullptr);
105 if (customPipelineLayout != VK_NULL_HANDLE) {
106 std::cout <<
"vk: destroying sprite custom pipeline layout\n";
107 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
110 destroyComputePipeline();
111 if (computeShaderModule != VK_NULL_HANDLE) {
112 vkDestroyShaderModule(device, computeShaderModule,
nullptr);
113 computeShaderModule = VK_NULL_HANDLE;
116 destroyExtendedUBO();
117 destroyHistoryTexture();
118 destroySpectrumTexture();
119 destroySpectrumHistoryTexture();
120 destroyInstanceResources();
123 void VK_Sprite::destroySpriteResources() {
124 destroyStagingResources();
126 destroyCudaInterop();
129 destroyTextureDescriptorPools();
132 std::cout <<
"vk: destroying sprite sampler\n";
137 if (!externalTexture && spriteImageView != VK_NULL_HANDLE) {
138 std::cout <<
"vk: destroying sprite image view\n";
139 vkDestroyImageView(device, spriteImageView,
nullptr);
140 spriteImageView = VK_NULL_HANDLE;
143 if (!externalTexture && spriteImage != VK_NULL_HANDLE) {
144 std::cout <<
"vk: destroying sprite image\n";
145 vkDestroyImage(device, spriteImage,
nullptr);
146 spriteImage = VK_NULL_HANDLE;
148 if (!externalTexture && spriteImageMemory != VK_NULL_HANDLE) {
149 std::cout <<
"vk: freeing sprite image memory\n";
150 vkFreeMemory(device, spriteImageMemory,
nullptr);
151 spriteImageMemory = VK_NULL_HANDLE;
153 externalTexture =
false;
155 if (fragmentShaderModule != VK_NULL_HANDLE) {
156 vkDestroyShaderModule(device, fragmentShaderModule,
nullptr);
157 fragmentShaderModule = VK_NULL_HANDLE;
160 if (customPipeline != VK_NULL_HANDLE) {
161 std::cout <<
"vk: destroying sprite custom pipeline\n";
162 vkDestroyPipeline(device, customPipeline,
nullptr);
163 customPipeline = VK_NULL_HANDLE;
166 if (customPipelineLayout != VK_NULL_HANDLE) {
167 std::cout <<
"vk: destroying sprite custom pipeline layout\n";
168 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
169 customPipelineLayout = VK_NULL_HANDLE;
172 hasCustomShader =
false;
173 spriteLoaded =
false;
177 if (extendedUBOEnabled)
179 extendedUBOEnabled =
true;
181 createExtendedDescriptorSetLayout();
186 extendedUBOData.mouse = glm::vec4(
mx, my, pressed, reserved);
190 extendedUBOData.u0 = glm::vec4(x, y, z, w);
194 extendedUBOData.u1 = glm::vec4(x, y, z, w);
198 extendedUBOData.u2 = glm::vec4(x, y, z, w);
202 extendedUBOData.u3 = glm::vec4(x, y, z, w);
206 extendedUBOData.audio_bands = glm::vec4(low, mid, high, reserved);
212 "VKSprite::setCustomUniforms supports at most {} values",
216 extendedUBOData.custom_uniforms.fill(glm::vec4(0.0f));
217 for (std::size_t index = 0; index < values.size(); ++index) {
218 extendedUBOData.custom_uniforms[index / 4][index % 4] = values[index];
223 if (width == 0 || height == 0 || layers == 0) {
224 throw mxvk::Exception(
"VKSprite::enableHistoryTexture requires positive dimensions and layer count");
227 if (historyTextureEnabled && historyWidth == width && historyHeight == height && historyLayers == layers) {
231 if (!extendedUBOEnabled) {
235 vkDeviceWaitIdle(device);
236 destroyHistoryTexture();
238 historyWidth = width;
239 historyHeight = height;
240 historyLayers = layers;
244 createCudaExportableImage(width, height, layers, historyImage,
246 cudaHistoryExportMemorySize);
247 cudaHistoryInteropUnavailableLogged =
false;
248 }
catch (
const std::exception &exception) {
249 std::cout << std::format(
250 "mxvk: CUDA exportable history image unavailable: {}; using "
251 "CPU staging uploads\n",
253 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM,
254 VK_IMAGE_TILING_OPTIMAL,
255 VK_IMAGE_USAGE_TRANSFER_DST_BIT |
256 VK_IMAGE_USAGE_SAMPLED_BIT,
257 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, historyImage,
258 historyImageMemory, layers);
261 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM,
262 VK_IMAGE_TILING_OPTIMAL,
263 VK_IMAGE_USAGE_TRANSFER_DST_BIT |
264 VK_IMAGE_USAGE_SAMPLED_BIT,
265 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, historyImage,
266 historyImageMemory, layers);
269 const VkDeviceSize layerSize =
static_cast<VkDeviceSize
>(width) * height * 4;
270 const VkDeviceSize imageSize = layerSize * layers;
271 VkBuffer stagingBuffer = VK_NULL_HANDLE;
272 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
273 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
274 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
275 stagingBuffer, stagingMemory);
277 void *data =
nullptr;
278 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
279 memset(data, 0,
static_cast<std::size_t
>(imageSize));
280 vkUnmapMemory(device, stagingMemory);
282 transitionImageLayout(historyImage, VK_IMAGE_LAYOUT_UNDEFINED,
283 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, layers);
285 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
286 std::vector<VkBufferImageCopy> regions(layers);
287 for (uint32_t layer = 0; layer < layers; ++layer) {
288 VkBufferImageCopy ®ion = regions[layer];
289 region.bufferOffset = layerSize * layer;
290 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
291 region.imageSubresource.mipLevel = 0;
292 region.imageSubresource.baseArrayLayer = layer;
293 region.imageSubresource.layerCount = 1;
294 region.imageExtent = {width, height, 1};
296 vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, historyImage,
297 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
298 static_cast<uint32_t
>(regions.size()), regions.data());
299 endSingleTimeCommands(commandBuffer);
301 transitionImageLayout(historyImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
302 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, layers);
303 vkDestroyBuffer(device, stagingBuffer,
nullptr);
304 vkFreeMemory(device, stagingMemory,
nullptr);
306 historyImageView = createImageView(historyImage, VK_FORMAT_R8G8B8A8_UNORM,
307 VK_IMAGE_VIEW_TYPE_2D_ARRAY, layers);
308 historyTextureEnabled =
true;
309 historyTextureShared =
false;
310 recreateExtendedDescriptorLayout();
314 if (source.device != device) {
316 "VKSprite::shareHistoryTexture requires sprites on the same Vulkan device");
318 if (!source.historyTextureEnabled ||
319 source.historyImageView == VK_NULL_HANDLE ||
320 source.historyLayers == 0) {
322 "VKSprite::shareHistoryTexture source has no enabled history texture");
324 if (&source ==
this) {
326 "VKSprite::shareHistoryTexture cannot share a sprite with itself");
329 if (!extendedUBOEnabled) {
333 vkDeviceWaitIdle(device);
334 destroyHistoryTexture();
335 historyImageView = source.historyImageView;
336 historyWidth = source.historyWidth;
337 historyHeight = source.historyHeight;
338 historyLayers = source.historyLayers;
339 historyHead = source.historyHead;
340 historyTextureEnabled =
true;
341 historyTextureShared =
true;
342 recreateExtendedDescriptorLayout();
346 if (!historyTextureEnabled || historyImage == VK_NULL_HANDLE) {
347 throw mxvk::Exception(
"VKSprite::updateHistoryTexture called before enableHistoryTexture");
349 if (pixels ==
nullptr) {
350 throw mxvk::Exception(
"VKSprite::updateHistoryTexture called with null pixel data");
352 if (width <= 0 || height <= 0 ||
static_cast<uint32_t
>(width) != historyWidth ||
353 static_cast<uint32_t
>(height) != historyHeight) {
354 throw mxvk::Exception(
"VKSprite::updateHistoryTexture dimensions do not match the history texture");
357 const int sourcePitch = pitch > 0 ? pitch : width * 4;
358 if (sourcePitch < width * 4) {
359 throw mxvk::Exception(
"VKSprite::updateHistoryTexture pitch is smaller than one RGBA row");
362 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) * height * 4;
363 createStagingResources(imageSize);
364 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
367 if (sourcePitch == width * 4) {
368 memcpy(persistentStagingMapped, pixels,
static_cast<std::size_t
>(imageSize));
370 const auto *source =
static_cast<const uint8_t *
>(pixels);
371 auto *destination =
static_cast<uint8_t *
>(persistentStagingMapped);
372 for (
int row = 0; row < height; ++row) {
373 memcpy(destination +
static_cast<std::size_t
>(row * width * 4),
374 source +
static_cast<std::size_t
>(row * sourcePitch),
375 static_cast<std::size_t
>(width * 4));
380 VkCommandBufferBeginInfo beginInfo{};
381 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
382 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
385 VkImageMemoryBarrier barrier{};
386 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
387 barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
388 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
389 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
390 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
391 barrier.image = historyImage;
392 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
393 barrier.subresourceRange.baseMipLevel = 0;
394 barrier.subresourceRange.levelCount = 1;
395 barrier.subresourceRange.baseArrayLayer = historyHead;
396 barrier.subresourceRange.layerCount = 1;
397 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
398 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
399 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
400 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
nullptr, 0,
nullptr, 1,
403 VkBufferImageCopy region{};
404 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
405 region.imageSubresource.mipLevel = 0;
406 region.imageSubresource.baseArrayLayer = historyHead;
407 region.imageSubresource.layerCount = 1;
408 region.imageExtent = {historyWidth, historyHeight, 1};
409 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer, historyImage,
410 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
412 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
413 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
414 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
415 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
416 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
417 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0,
nullptr, 0,
nullptr,
421 VkSubmitInfo submitInfo{};
422 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
423 submitInfo.commandBufferCount = 1;
424 submitInfo.pCommandBuffers = &uploadCmdBuffer;
425 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
426 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
428 historyHead = (historyHead + 1) % historyLayers;
433 throw mxvk::Exception(
"VKSprite::enableSpectrumTexture requires a positive bin count");
435 if (spectrumTextureEnabled && spectrumBins == bins) {
438 if (!extendedUBOEnabled) {
442 vkDeviceWaitIdle(device);
443 destroySpectrumTexture();
446 createImage(bins, 1, VK_FORMAT_R32_SFLOAT, VK_IMAGE_TILING_OPTIMAL,
447 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
448 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spectrumImage,
449 spectrumImageMemory, 1, VK_IMAGE_TYPE_1D);
451 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(bins) *
sizeof(
float);
452 VkBuffer stagingBuffer = VK_NULL_HANDLE;
453 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
454 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
455 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
456 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
457 stagingBuffer, stagingMemory);
459 void *data =
nullptr;
460 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
461 memset(data, 0,
static_cast<std::size_t
>(imageSize));
462 vkUnmapMemory(device, stagingMemory);
464 transitionImageLayout(spectrumImage, VK_IMAGE_LAYOUT_UNDEFINED,
465 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
466 copyBufferToImage(stagingBuffer, spectrumImage, bins, 1);
467 transitionImageLayout(spectrumImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
468 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
470 vkDestroyBuffer(device, stagingBuffer,
nullptr);
471 vkFreeMemory(device, stagingMemory,
nullptr);
473 spectrumImageView = createImageView(spectrumImage, VK_FORMAT_R32_SFLOAT,
474 VK_IMAGE_VIEW_TYPE_1D);
475 spectrumTextureEnabled =
true;
476 recreateExtendedDescriptorLayout();
480 if (!spectrumTextureEnabled || spectrumImage == VK_NULL_HANDLE) {
481 throw mxvk::Exception(
"VKSprite::updateSpectrumTexture called before enableSpectrumTexture");
483 if (magnitudes ==
nullptr) {
484 throw mxvk::Exception(
"VKSprite::updateSpectrumTexture called with null data");
486 if (bins != spectrumBins) {
487 throw mxvk::Exception(
"VKSprite::updateSpectrumTexture bin count does not match the spectrum texture");
490 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(bins) *
sizeof(
float);
491 createStagingResources(imageSize);
492 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
494 memcpy(persistentStagingMapped, magnitudes,
static_cast<std::size_t
>(imageSize));
497 VkCommandBufferBeginInfo beginInfo{};
498 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
499 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
502 VkImageMemoryBarrier barrier{};
503 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
504 barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
505 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
506 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
507 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
508 barrier.image = spectrumImage;
509 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
510 barrier.subresourceRange.baseMipLevel = 0;
511 barrier.subresourceRange.levelCount = 1;
512 barrier.subresourceRange.baseArrayLayer = 0;
513 barrier.subresourceRange.layerCount = 1;
514 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
515 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
516 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
517 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
nullptr, 0,
nullptr, 1,
520 VkBufferImageCopy region{};
521 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
522 region.imageSubresource.mipLevel = 0;
523 region.imageSubresource.baseArrayLayer = 0;
524 region.imageSubresource.layerCount = 1;
525 region.imageExtent = {bins, 1, 1};
526 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer, spectrumImage,
527 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
529 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
530 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
531 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
532 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
533 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
534 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0,
nullptr, 0,
nullptr,
538 VkSubmitInfo submitInfo{};
539 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
540 submitInfo.commandBufferCount = 1;
541 submitInfo.pCommandBuffers = &uploadCmdBuffer;
542 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
543 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
547 if (bins == 0 || layers == 0) {
549 "VKSprite::enableSpectrumHistoryTexture requires positive bin and layer counts");
551 VkPhysicalDeviceProperties properties{};
552 vkGetPhysicalDeviceProperties(physicalDevice, &properties);
553 const uint32_t allocatedLayers =
554 std::min(layers, properties.limits.maxImageArrayLayers);
555 if (allocatedLayers == 0) {
557 "VKSprite::enableSpectrumHistoryTexture is unavailable on this device");
559 if (spectrumHistoryTextureEnabled && spectrumHistoryBins == bins &&
560 spectrumHistoryLayers == allocatedLayers) {
561 return spectrumHistoryLayers;
563 if (!extendedUBOEnabled) {
567 vkDeviceWaitIdle(device);
568 destroySpectrumHistoryTexture();
570 if (allocatedLayers != layers) {
571 std::cerr <<
"vk: spectrum history clamped to device array-layer limit "
572 << allocatedLayers <<
" (was " << layers <<
")\n";
574 spectrumHistoryBins = bins;
575 spectrumHistoryLayers = allocatedLayers;
576 spectrumHistoryHead = 0;
577 spectrumHistoryWriteIndex = 0;
578 extendedUBOData.audio_history =
579 glm::vec4(0.0f,
static_cast<float>(allocatedLayers),
580 static_cast<float>(bins), 0.0f);
582 createImage(bins, 1, VK_FORMAT_R32_SFLOAT, VK_IMAGE_TILING_OPTIMAL,
583 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
584 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spectrumHistoryImage,
585 spectrumHistoryImageMemory, allocatedLayers, VK_IMAGE_TYPE_1D);
587 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(bins) *
588 static_cast<VkDeviceSize
>(allocatedLayers) *
590 VkBuffer stagingBuffer = VK_NULL_HANDLE;
591 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
592 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
593 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
594 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
595 stagingBuffer, stagingMemory);
597 void *data =
nullptr;
598 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
599 memset(data, 0,
static_cast<std::size_t
>(imageSize));
600 vkUnmapMemory(device, stagingMemory);
602 transitionImageLayout(spectrumHistoryImage, VK_IMAGE_LAYOUT_UNDEFINED,
603 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0,
605 copyBufferToImage(stagingBuffer, spectrumHistoryImage, bins, 1, 0,
607 transitionImageLayout(spectrumHistoryImage,
608 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
609 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0,
612 vkDestroyBuffer(device, stagingBuffer,
nullptr);
613 vkFreeMemory(device, stagingMemory,
nullptr);
615 spectrumHistoryImageView =
616 createImageView(spectrumHistoryImage, VK_FORMAT_R32_SFLOAT,
617 VK_IMAGE_VIEW_TYPE_1D_ARRAY, allocatedLayers);
618 spectrumHistoryTextureEnabled =
true;
619 recreateExtendedDescriptorLayout();
620 return allocatedLayers;
624 if (!spectrumHistoryTextureEnabled || spectrumHistoryImage == VK_NULL_HANDLE) {
626 "VKSprite::updateSpectrumHistoryTexture called before enableSpectrumHistoryTexture");
628 if (magnitudes ==
nullptr) {
630 "VKSprite::updateSpectrumHistoryTexture called with null data");
632 if (bins != spectrumHistoryBins) {
634 "VKSprite::updateSpectrumHistoryTexture bin count does not match the history texture");
637 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(bins) *
sizeof(
float);
638 createStagingResources(imageSize);
639 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
641 memcpy(persistentStagingMapped, magnitudes,
static_cast<std::size_t
>(imageSize));
644 VkCommandBufferBeginInfo beginInfo{};
645 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
646 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
649 VkImageMemoryBarrier barrier{};
650 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
651 barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
652 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
653 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
654 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
655 barrier.image = spectrumHistoryImage;
656 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
657 barrier.subresourceRange.baseMipLevel = 0;
658 barrier.subresourceRange.levelCount = 1;
659 barrier.subresourceRange.baseArrayLayer = spectrumHistoryWriteIndex;
660 barrier.subresourceRange.layerCount = 1;
661 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
662 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
663 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
664 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
nullptr, 0,
665 nullptr, 1, &barrier);
667 VkBufferImageCopy region{};
668 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
669 region.imageSubresource.mipLevel = 0;
670 region.imageSubresource.baseArrayLayer = spectrumHistoryWriteIndex;
671 region.imageSubresource.layerCount = 1;
672 region.imageExtent = {bins, 1, 1};
673 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer,
674 spectrumHistoryImage,
675 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
677 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
678 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
679 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
680 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
681 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
682 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0,
nullptr,
683 0,
nullptr, 1, &barrier);
686 VkSubmitInfo submitInfo{};
687 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
688 submitInfo.commandBufferCount = 1;
689 submitInfo.pCommandBuffers = &uploadCmdBuffer;
690 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
691 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
693 spectrumHistoryHead = spectrumHistoryWriteIndex;
694 spectrumHistoryWriteIndex =
695 (spectrumHistoryWriteIndex + 1) % spectrumHistoryLayers;
696 extendedUBOData.audio_history =
697 glm::vec4(
static_cast<float>(spectrumHistoryHead),
698 static_cast<float>(spectrumHistoryLayers),
699 static_cast<float>(spectrumHistoryBins), 0.0f);
702 void VK_Sprite::createExtendedUBO() {
703 if (extendedUBOBuffer != VK_NULL_HANDLE)
705 createBuffer(
sizeof(SpriteExtendedUBO), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
706 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
707 extendedUBOBuffer, extendedUBOMemory);
708 VK_CHECK_RESULT(vkMapMemory(device, extendedUBOMemory, 0,
sizeof(SpriteExtendedUBO), 0, &extendedUBOMapped));
709 memset(extendedUBOMapped, 0,
sizeof(SpriteExtendedUBO));
712 void VK_Sprite::updateExtendedUBO() {
713 if (!extendedUBOEnabled || !extendedUBOMapped)
715 memcpy(extendedUBOMapped, &extendedUBOData,
sizeof(SpriteExtendedUBO));
718 void VK_Sprite::createExtendedDescriptorSetLayout() {
719 if (extendedDescriptorSetLayout != VK_NULL_HANDLE)
722 std::vector<VkDescriptorSetLayoutBinding> bindings(2);
724 bindings[0].binding = 0;
725 bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
726 bindings[0].descriptorCount = 1;
727 bindings[0].stageFlags =
728 VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
729 bindings[0].pImmutableSamplers =
nullptr;
731 bindings[1].binding = 1;
732 bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
733 bindings[1].descriptorCount = 1;
734 bindings[1].stageFlags =
735 VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
736 bindings[1].pImmutableSamplers =
nullptr;
737 if (historyTextureEnabled) {
738 VkDescriptorSetLayoutBinding historyBinding{};
739 historyBinding.binding = 2;
740 historyBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
741 historyBinding.descriptorCount = 1;
742 historyBinding.stageFlags =
743 VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
744 bindings.push_back(historyBinding);
746 if (spectrumTextureEnabled) {
747 VkDescriptorSetLayoutBinding spectrumBinding{};
748 spectrumBinding.binding = 3;
749 spectrumBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
750 spectrumBinding.descriptorCount = 1;
751 spectrumBinding.stageFlags =
752 VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
753 bindings.push_back(spectrumBinding);
755 if (spectrumHistoryTextureEnabled) {
756 VkDescriptorSetLayoutBinding spectrumHistoryBinding{};
757 spectrumHistoryBinding.binding = 4;
758 spectrumHistoryBinding.descriptorType =
759 VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
760 spectrumHistoryBinding.descriptorCount = 1;
761 spectrumHistoryBinding.stageFlags =
762 VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
763 bindings.push_back(spectrumHistoryBinding);
765 if (computeShaderModule != VK_NULL_HANDLE) {
766 VkDescriptorSetLayoutBinding outputBinding{};
767 outputBinding.binding = 5;
768 outputBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
769 outputBinding.descriptorCount = 1;
770 outputBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
771 bindings.push_back(outputBinding);
774 VkDescriptorSetLayoutCreateInfo layoutInfo{};
775 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
776 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
777 layoutInfo.pBindings = bindings.data();
779 VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &layoutInfo,
nullptr, &extendedDescriptorSetLayout));
780 ownExtendedDescriptorSetLayout =
true;
783 void VK_Sprite::createExtendedDescriptorSet() {
784 if (extendedDescriptorSetLayout == VK_NULL_HANDLE || spriteImageView == VK_NULL_HANDLE ||
785 spriteSampler == VK_NULL_HANDLE || extendedUBOBuffer == VK_NULL_HANDLE ||
786 (historyTextureEnabled && historyImageView == VK_NULL_HANDLE) ||
787 (spectrumTextureEnabled && spectrumImageView == VK_NULL_HANDLE) ||
788 (spectrumHistoryTextureEnabled &&
789 spectrumHistoryImageView == VK_NULL_HANDLE) ||
790 (computeShaderModule != VK_NULL_HANDLE &&
791 computeOutputImageView == VK_NULL_HANDLE))
794 if (extendedDescriptorPool != VK_NULL_HANDLE) {
795 vkDeviceWaitIdle(device);
796 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
797 extendedDescriptorPool = VK_NULL_HANDLE;
798 extendedDescriptorSet = VK_NULL_HANDLE;
801 std::array<VkDescriptorPoolSize, 3> poolSizes{};
802 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
803 poolSizes[0].descriptorCount = 1U +
static_cast<uint32_t
>(historyTextureEnabled) +
804 static_cast<uint32_t
>(spectrumTextureEnabled) +
805 static_cast<uint32_t
>(spectrumHistoryTextureEnabled);
806 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
807 poolSizes[1].descriptorCount = 1;
808 poolSizes[2].type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
809 poolSizes[2].descriptorCount =
810 computeShaderModule != VK_NULL_HANDLE ? 1U : 0U;
812 VkDescriptorPoolCreateInfo poolInfo{};
813 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
814 poolInfo.poolSizeCount = computeShaderModule != VK_NULL_HANDLE
815 ?
static_cast<uint32_t
>(poolSizes.size())
817 poolInfo.pPoolSizes = poolSizes.data();
818 poolInfo.maxSets = 1;
820 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo,
nullptr, &extendedDescriptorPool));
822 VkDescriptorSetAllocateInfo allocInfo{};
823 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
824 allocInfo.descriptorPool = extendedDescriptorPool;
825 allocInfo.descriptorSetCount = 1;
826 allocInfo.pSetLayouts = &extendedDescriptorSetLayout;
828 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &extendedDescriptorSet));
830 VkDescriptorImageInfo imageInfo{};
831 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
832 imageInfo.imageView = spriteImageView;
835 VkDescriptorBufferInfo bufferInfo{};
836 bufferInfo.buffer = extendedUBOBuffer;
837 bufferInfo.offset = 0;
838 bufferInfo.range =
sizeof(SpriteExtendedUBO);
840 VkDescriptorImageInfo historyImageInfo{};
841 historyImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
842 historyImageInfo.imageView = historyImageView;
845 VkDescriptorImageInfo spectrumImageInfo{};
846 spectrumImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
847 spectrumImageInfo.imageView = spectrumImageView;
850 VkDescriptorImageInfo spectrumHistoryImageInfo{};
851 spectrumHistoryImageInfo.imageLayout =
852 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
853 spectrumHistoryImageInfo.imageView = spectrumHistoryImageView;
856 VkDescriptorImageInfo outputImageInfo{};
857 outputImageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
858 outputImageInfo.imageView = computeOutputImageView;
860 std::vector<VkWriteDescriptorSet> writes(2);
861 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
862 writes[0].dstSet = extendedDescriptorSet;
863 writes[0].dstBinding = 0;
864 writes[0].dstArrayElement = 0;
865 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
866 writes[0].descriptorCount = 1;
867 writes[0].pImageInfo = &imageInfo;
869 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
870 writes[1].dstSet = extendedDescriptorSet;
871 writes[1].dstBinding = 1;
872 writes[1].dstArrayElement = 0;
873 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
874 writes[1].descriptorCount = 1;
875 writes[1].pBufferInfo = &bufferInfo;
877 if (historyTextureEnabled) {
878 VkWriteDescriptorSet historyWrite{};
879 historyWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
880 historyWrite.dstSet = extendedDescriptorSet;
881 historyWrite.dstBinding = 2;
882 historyWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
883 historyWrite.descriptorCount = 1;
884 historyWrite.pImageInfo = &historyImageInfo;
885 writes.push_back(historyWrite);
887 if (spectrumTextureEnabled) {
888 VkWriteDescriptorSet spectrumWrite{};
889 spectrumWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
890 spectrumWrite.dstSet = extendedDescriptorSet;
891 spectrumWrite.dstBinding = 3;
892 spectrumWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
893 spectrumWrite.descriptorCount = 1;
894 spectrumWrite.pImageInfo = &spectrumImageInfo;
895 writes.push_back(spectrumWrite);
897 if (spectrumHistoryTextureEnabled) {
898 VkWriteDescriptorSet spectrumHistoryWrite{};
899 spectrumHistoryWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
900 spectrumHistoryWrite.dstSet = extendedDescriptorSet;
901 spectrumHistoryWrite.dstBinding = 4;
902 spectrumHistoryWrite.descriptorType =
903 VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
904 spectrumHistoryWrite.descriptorCount = 1;
905 spectrumHistoryWrite.pImageInfo = &spectrumHistoryImageInfo;
906 writes.push_back(spectrumHistoryWrite);
908 if (computeShaderModule != VK_NULL_HANDLE) {
909 VkWriteDescriptorSet outputWrite{};
910 outputWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
911 outputWrite.dstSet = extendedDescriptorSet;
912 outputWrite.dstBinding = 5;
913 outputWrite.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
914 outputWrite.descriptorCount = 1;
915 outputWrite.pImageInfo = &outputImageInfo;
916 writes.push_back(outputWrite);
919 vkUpdateDescriptorSets(device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
923 void VK_Sprite::recreateExtendedDescriptorLayout() {
924 vkDeviceWaitIdle(device);
926 if (customPipeline != VK_NULL_HANDLE) {
927 vkDestroyPipeline(device, customPipeline,
nullptr);
928 customPipeline = VK_NULL_HANDLE;
930 if (customPipelineLayout != VK_NULL_HANDLE) {
931 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
932 customPipelineLayout = VK_NULL_HANDLE;
934 destroyComputePipeline();
935 if (extendedDescriptorPool != VK_NULL_HANDLE) {
936 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
937 extendedDescriptorPool = VK_NULL_HANDLE;
938 extendedDescriptorSet = VK_NULL_HANDLE;
940 if (ownExtendedDescriptorSetLayout && extendedDescriptorSetLayout != VK_NULL_HANDLE) {
941 vkDestroyDescriptorSetLayout(device, extendedDescriptorSetLayout,
nullptr);
942 extendedDescriptorSetLayout = VK_NULL_HANDLE;
943 ownExtendedDescriptorSetLayout =
false;
946 createExtendedDescriptorSetLayout();
948 createComputePipeline();
951 void VK_Sprite::destroyHistoryTexture() {
953 if (!historyTextureShared) {
954 destroyCudaHistoryInterop();
957 if (!historyTextureShared && historyImageView != VK_NULL_HANDLE) {
958 vkDestroyImageView(device, historyImageView,
nullptr);
960 historyImageView = VK_NULL_HANDLE;
961 if (historyImage != VK_NULL_HANDLE) {
962 vkDestroyImage(device, historyImage,
nullptr);
963 historyImage = VK_NULL_HANDLE;
965 if (historyImageMemory != VK_NULL_HANDLE) {
966 vkFreeMemory(device, historyImageMemory,
nullptr);
967 historyImageMemory = VK_NULL_HANDLE;
969 historyTextureEnabled =
false;
970 historyTextureShared =
false;
977 void VK_Sprite::destroySpectrumTexture() {
978 if (spectrumImageView != VK_NULL_HANDLE) {
979 vkDestroyImageView(device, spectrumImageView,
nullptr);
980 spectrumImageView = VK_NULL_HANDLE;
982 if (spectrumImage != VK_NULL_HANDLE) {
983 vkDestroyImage(device, spectrumImage,
nullptr);
984 spectrumImage = VK_NULL_HANDLE;
986 if (spectrumImageMemory != VK_NULL_HANDLE) {
987 vkFreeMemory(device, spectrumImageMemory,
nullptr);
988 spectrumImageMemory = VK_NULL_HANDLE;
990 spectrumTextureEnabled =
false;
994 void VK_Sprite::destroySpectrumHistoryTexture() {
995 if (spectrumHistoryImageView != VK_NULL_HANDLE) {
996 vkDestroyImageView(device, spectrumHistoryImageView,
nullptr);
997 spectrumHistoryImageView = VK_NULL_HANDLE;
999 if (spectrumHistoryImage != VK_NULL_HANDLE) {
1000 vkDestroyImage(device, spectrumHistoryImage,
nullptr);
1001 spectrumHistoryImage = VK_NULL_HANDLE;
1003 if (spectrumHistoryImageMemory != VK_NULL_HANDLE) {
1004 vkFreeMemory(device, spectrumHistoryImageMemory,
nullptr);
1005 spectrumHistoryImageMemory = VK_NULL_HANDLE;
1007 spectrumHistoryTextureEnabled =
false;
1008 spectrumHistoryBins = 0;
1009 spectrumHistoryLayers = 0;
1010 spectrumHistoryHead = 0;
1011 spectrumHistoryWriteIndex = 0;
1012 extendedUBOData.audio_history = glm::vec4(0.0f);
1015 void VK_Sprite::destroyExtendedUBO() {
1016 if (extendedDescriptorPool != VK_NULL_HANDLE) {
1017 vkDeviceWaitIdle(device);
1018 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
1019 extendedDescriptorPool = VK_NULL_HANDLE;
1020 extendedDescriptorSet = VK_NULL_HANDLE;
1022 if (ownExtendedDescriptorSetLayout && extendedDescriptorSetLayout != VK_NULL_HANDLE) {
1023 vkDestroyDescriptorSetLayout(device, extendedDescriptorSetLayout,
nullptr);
1024 extendedDescriptorSetLayout = VK_NULL_HANDLE;
1025 ownExtendedDescriptorSetLayout =
false;
1027 if (extendedUBOBuffer != VK_NULL_HANDLE) {
1028 if (extendedUBOMapped) {
1029 vkUnmapMemory(device, extendedUBOMemory);
1030 extendedUBOMapped =
nullptr;
1032 vkDestroyBuffer(device, extendedUBOBuffer,
nullptr);
1033 vkFreeMemory(device, extendedUBOMemory,
nullptr);
1034 extendedUBOBuffer = VK_NULL_HANDLE;
1035 extendedUBOMemory = VK_NULL_HANDLE;
1037 extendedUBOEnabled =
false;
1040 VkDeviceSize VK_Sprite::stagingAllocationSize(VkDeviceSize requiredSize)
const {
1041 VkDeviceSize allocationSize = 1;
1042 while (allocationSize < requiredSize && allocationSize <= (std::numeric_limits<VkDeviceSize>::max() / 2)) {
1043 allocationSize *= 2;
1045 return std::max(allocationSize, requiredSize);
1048 void VK_Sprite::createStagingResources(VkDeviceSize size) {
1049 const VkDeviceSize allocationSize = stagingAllocationSize(size);
1050 if (stagingResourcesCreated && persistentStagingSize >= size) {
1053 destroyStagingResources();
1055 createBuffer(allocationSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
1056 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1057 persistentStagingBuffer, persistentStagingMemory);
1060 VK_CHECK_RESULT(vkMapMemory(device, persistentStagingMemory, 0, allocationSize, 0, &persistentStagingMapped));
1061 persistentStagingSize = allocationSize;
1062 VkCommandBufferAllocateInfo allocInfo{};
1063 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1064 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1065 allocInfo.commandPool = commandPool;
1066 allocInfo.commandBufferCount = 1;
1067 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &uploadCmdBuffer));
1068 VkFenceCreateInfo fenceInfo{};
1069 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1070 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
1071 VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo,
nullptr, &uploadFence));
1072 stagingResourcesCreated =
true;
1074 if (uploadCmdBuffer != VK_NULL_HANDLE) {
1075 vkFreeCommandBuffers(device, commandPool, 1, &uploadCmdBuffer);
1076 uploadCmdBuffer = VK_NULL_HANDLE;
1078 if (persistentStagingMapped) {
1079 vkUnmapMemory(device, persistentStagingMemory);
1080 persistentStagingMapped =
nullptr;
1082 if (persistentStagingBuffer != VK_NULL_HANDLE) {
1083 vkDestroyBuffer(device, persistentStagingBuffer,
nullptr);
1084 persistentStagingBuffer = VK_NULL_HANDLE;
1086 if (persistentStagingMemory != VK_NULL_HANDLE) {
1087 vkFreeMemory(device, persistentStagingMemory,
nullptr);
1088 persistentStagingMemory = VK_NULL_HANDLE;
1090 persistentStagingSize = 0;
1095 void VK_Sprite::destroyStagingResources() {
1096 if (!stagingResourcesCreated)
1099 if (uploadFence != VK_NULL_HANDLE) {
1100 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1101 vkDestroyFence(device, uploadFence,
nullptr);
1102 uploadFence = VK_NULL_HANDLE;
1104 if (uploadCmdBuffer != VK_NULL_HANDLE) {
1105 vkFreeCommandBuffers(device, commandPool, 1, &uploadCmdBuffer);
1106 uploadCmdBuffer = VK_NULL_HANDLE;
1108 if (persistentStagingBuffer != VK_NULL_HANDLE) {
1109 vkUnmapMemory(device, persistentStagingMemory);
1110 vkDestroyBuffer(device, persistentStagingBuffer,
nullptr);
1111 vkFreeMemory(device, persistentStagingMemory,
nullptr);
1112 persistentStagingBuffer = VK_NULL_HANDLE;
1113 persistentStagingMemory = VK_NULL_HANDLE;
1114 persistentStagingMapped =
nullptr;
1115 persistentStagingSize = 0;
1117 stagingResourcesCreated =
false;
1120 void VK_Sprite::destroyInstanceResources() {
1121 if (instanceBuffer != VK_NULL_HANDLE) {
1122 if (instanceBufferMapped) {
1123 vkUnmapMemory(device, instanceBufferMemory);
1124 instanceBufferMapped =
nullptr;
1126 vkDestroyBuffer(device, instanceBuffer,
nullptr);
1127 vkFreeMemory(device, instanceBufferMemory,
nullptr);
1128 instanceBuffer = VK_NULL_HANDLE;
1129 instanceBufferMemory = VK_NULL_HANDLE;
1130 instanceBufferCapacity = 0;
1132 if (instancedPipeline != VK_NULL_HANDLE) {
1133 vkDestroyPipeline(device, instancedPipeline,
nullptr);
1134 instancedPipeline = VK_NULL_HANDLE;
1136 if (instancedPipelineLayout != VK_NULL_HANDLE) {
1137 vkDestroyPipelineLayout(device, instancedPipelineLayout,
nullptr);
1138 instancedPipelineLayout = VK_NULL_HANDLE;
1140 instancingEnabled =
false;
1143 void VK_Sprite::ensureInstanceBuffer(uint32_t count) {
1144 if (instanceBufferCapacity >= count && instanceBuffer != VK_NULL_HANDLE)
1147 if (instanceBuffer != VK_NULL_HANDLE) {
1148 if (instanceBufferMapped) {
1149 vkUnmapMemory(device, instanceBufferMemory);
1150 instanceBufferMapped =
nullptr;
1152 vkDestroyBuffer(device, instanceBuffer,
nullptr);
1153 vkFreeMemory(device, instanceBufferMemory,
nullptr);
1154 instanceBuffer = VK_NULL_HANDLE;
1155 instanceBufferMemory = VK_NULL_HANDLE;
1158 VkDeviceSize size =
sizeof(SpriteInstanceData) * count;
1159 createBuffer(size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
1160 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1161 instanceBuffer, instanceBufferMemory);
1163 VK_CHECK_RESULT(vkMapMemory(device, instanceBufferMemory, 0, size, 0, &instanceBufferMapped));
1164 instanceBufferCapacity = count;
1168 const std::string &instanceVertShaderPath,
1169 const std::string &instanceFragShaderPath) {
1170 if (colorAttachmentFormat == VK_FORMAT_UNDEFINED || descriptorSetLayout == VK_NULL_HANDLE) {
1171 throw mxvk::Exception(
"VKSprite::enableInstancing called before color format/descriptorSetLayout set");
1173 ensureInstanceBuffer(maxInstances);
1175 instanceVertPath = instanceVertShaderPath;
1176 instanceFragPath = instanceFragShaderPath;
1177 createInstancedPipeline(instanceVertShaderPath, instanceFragShaderPath);
1178 instancingEnabled =
true;
1179 std::cout << std::format(
"mxvk: Instancing enabled (max {} instances)\n", maxInstances);
1182 void VK_Sprite::createInstancedPipeline(
const std::string &vertPath,
const std::string &fragPath) {
1183 if (instancedPipeline != VK_NULL_HANDLE) {
1184 vkDestroyPipeline(device, instancedPipeline,
nullptr);
1185 instancedPipeline = VK_NULL_HANDLE;
1187 if (instancedPipelineLayout != VK_NULL_HANDLE) {
1188 vkDestroyPipelineLayout(device, instancedPipelineLayout,
nullptr);
1189 instancedPipelineLayout = VK_NULL_HANDLE;
1192 auto vertShaderCode = readShaderFile(vertPath);
1193 auto fragShaderCode = readShaderFile(fragPath);
1197 VkPipelineShaderStageCreateInfo vertStageInfo{};
1198 vertStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1199 vertStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
1200 vertStageInfo.module = vertModule;
1201 vertStageInfo.pName =
"main";
1203 VkPipelineShaderStageCreateInfo fragStageInfo{};
1204 fragStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1205 fragStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1206 fragStageInfo.module = fragModule;
1207 fragStageInfo.pName =
"main";
1209 VkPipelineShaderStageCreateInfo shaderStages[] = {vertStageInfo, fragStageInfo};
1211 std::array<VkVertexInputBindingDescription, 2> bindingDescs{};
1212 bindingDescs[0].binding = 0;
1213 bindingDescs[0].stride =
sizeof(float) * 4;
1214 bindingDescs[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1215 bindingDescs[1].binding = 1;
1216 bindingDescs[1].stride =
sizeof(SpriteInstanceData);
1217 bindingDescs[1].inputRate = VK_VERTEX_INPUT_RATE_INSTANCE;
1219 std::array<VkVertexInputAttributeDescription, 4> attrDescs{};
1221 attrDescs[0].binding = 0;
1222 attrDescs[0].location = 0;
1223 attrDescs[0].format = VK_FORMAT_R32G32_SFLOAT;
1224 attrDescs[0].offset = 0;
1226 attrDescs[1].binding = 0;
1227 attrDescs[1].location = 1;
1228 attrDescs[1].format = VK_FORMAT_R32G32_SFLOAT;
1229 attrDescs[1].offset =
sizeof(float) * 2;
1231 attrDescs[2].binding = 1;
1232 attrDescs[2].location = 2;
1233 attrDescs[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
1234 attrDescs[2].offset = 0;
1236 attrDescs[3].binding = 1;
1237 attrDescs[3].location = 3;
1238 attrDescs[3].format = VK_FORMAT_R32G32B32A32_SFLOAT;
1239 attrDescs[3].offset =
sizeof(float) * 4;
1241 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
1242 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1243 vertexInputInfo.vertexBindingDescriptionCount =
static_cast<uint32_t
>(bindingDescs.size());
1244 vertexInputInfo.pVertexBindingDescriptions = bindingDescs.data();
1245 vertexInputInfo.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrDescs.size());
1246 vertexInputInfo.pVertexAttributeDescriptions = attrDescs.data();
1248 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1249 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1250 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1251 inputAssembly.primitiveRestartEnable = VK_FALSE;
1253 std::vector<VkDynamicState> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
1254 VkPipelineDynamicStateCreateInfo dynamicState{};
1255 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1256 dynamicState.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1257 dynamicState.pDynamicStates = dynamicStates.data();
1259 VkPipelineViewportStateCreateInfo viewportState{};
1260 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1261 viewportState.viewportCount = 1;
1262 viewportState.scissorCount = 1;
1264 VkPipelineRasterizationStateCreateInfo rasterizer{};
1265 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1266 rasterizer.depthClampEnable = VK_FALSE;
1267 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1268 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1269 rasterizer.lineWidth = 1.0f;
1270 rasterizer.cullMode = VK_CULL_MODE_NONE;
1271 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1272 rasterizer.depthBiasEnable = VK_FALSE;
1274 VkPipelineMultisampleStateCreateInfo multisampling{};
1275 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1276 multisampling.sampleShadingEnable = VK_FALSE;
1277 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1279 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1280 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1281 depthStencil.depthTestEnable = VK_FALSE;
1282 depthStencil.depthWriteEnable = VK_FALSE;
1284 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
1285 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
1286 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1287 colorBlendAttachment.blendEnable = VK_TRUE;
1288 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
1289 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1290 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
1291 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
1292 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1293 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
1295 VkPipelineColorBlendStateCreateInfo colorBlending{};
1296 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1297 colorBlending.logicOpEnable = VK_FALSE;
1298 colorBlending.attachmentCount = 1;
1299 colorBlending.pAttachments = &colorBlendAttachment;
1301 VkPushConstantRange pushConstantRange{};
1302 pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1303 pushConstantRange.offset = 0;
1304 pushConstantRange.size =
sizeof(float) * 2;
1306 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
1307 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1308 pipelineLayoutInfo.setLayoutCount = 1;
1309 pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
1310 pipelineLayoutInfo.pushConstantRangeCount = 1;
1311 pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
1313 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo,
nullptr, &instancedPipelineLayout));
1315 VkGraphicsPipelineCreateInfo pipelineInfo{};
1316 VkPipelineRenderingCreateInfo renderingInfo{};
1317 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1318 renderingInfo.viewMask = 0;
1319 renderingInfo.colorAttachmentCount = 1;
1320 renderingInfo.pColorAttachmentFormats = &colorAttachmentFormat;
1321 if (depthAttachmentFormat != VK_FORMAT_UNDEFINED) {
1322 renderingInfo.depthAttachmentFormat = depthAttachmentFormat;
1325 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1326 pipelineInfo.pNext = &renderingInfo;
1327 pipelineInfo.stageCount = 2;
1328 pipelineInfo.pStages = shaderStages;
1329 pipelineInfo.pVertexInputState = &vertexInputInfo;
1330 pipelineInfo.pInputAssemblyState = &inputAssembly;
1331 pipelineInfo.pViewportState = &viewportState;
1332 pipelineInfo.pRasterizationState = &rasterizer;
1333 pipelineInfo.pMultisampleState = &multisampling;
1334 pipelineInfo.pDepthStencilState = &depthStencil;
1335 pipelineInfo.pColorBlendState = &colorBlending;
1336 pipelineInfo.pDynamicState = &dynamicState;
1337 pipelineInfo.layout = instancedPipelineLayout;
1338 pipelineInfo.renderPass = VK_NULL_HANDLE;
1339 pipelineInfo.subpass = 0;
1340 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
1342 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo,
nullptr, &instancedPipeline));
1344 vkDestroyShaderModule(device, vertModule,
nullptr);
1345 vkDestroyShaderModule(device, fragModule,
nullptr);
1348 void VK_Sprite::createCustomPipeline() {
1349 if (!hasCustomShader || fragmentShaderModule == VK_NULL_HANDLE)
1351 if (colorAttachmentFormat == VK_FORMAT_UNDEFINED || descriptorSetLayout == VK_NULL_HANDLE)
1354 if (customPipeline != VK_NULL_HANDLE) {
1355 vkDestroyPipeline(device, customPipeline,
nullptr);
1356 customPipeline = VK_NULL_HANDLE;
1358 if (customPipelineLayout != VK_NULL_HANDLE) {
1359 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
1360 customPipelineLayout = VK_NULL_HANDLE;
1363 std::string vertPath = vertexShaderPath.empty() ?
"sprite.vert.spv" : vertexShaderPath;
1364 auto vertShaderCode = readShaderFile(vertPath);
1367 VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
1368 vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1369 vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
1370 vertShaderStageInfo.module = vertShaderModule;
1371 vertShaderStageInfo.pName =
"main";
1373 VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
1374 fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1375 fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1376 fragShaderStageInfo.module = fragmentShaderModule;
1377 fragShaderStageInfo.pName =
"main";
1379 VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
1381 VkVertexInputBindingDescription bindingDescription{};
1382 bindingDescription.binding = 0;
1383 bindingDescription.stride =
sizeof(float) * 4;
1384 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1386 std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
1387 attributeDescriptions[0].binding = 0;
1388 attributeDescriptions[0].location = 0;
1389 attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
1390 attributeDescriptions[0].offset = 0;
1391 attributeDescriptions[1].binding = 0;
1392 attributeDescriptions[1].location = 1;
1393 attributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;
1394 attributeDescriptions[1].offset =
sizeof(float) * 2;
1396 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
1397 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1398 vertexInputInfo.vertexBindingDescriptionCount = 1;
1399 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
1400 vertexInputInfo.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributeDescriptions.size());
1401 vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
1403 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1404 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1405 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1406 inputAssembly.primitiveRestartEnable = VK_FALSE;
1408 std::vector<VkDynamicState> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
1409 VkPipelineDynamicStateCreateInfo dynamicState{};
1410 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1411 dynamicState.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1412 dynamicState.pDynamicStates = dynamicStates.data();
1414 VkPipelineViewportStateCreateInfo viewportState{};
1415 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1416 viewportState.viewportCount = 1;
1417 viewportState.scissorCount = 1;
1419 VkPipelineRasterizationStateCreateInfo rasterizer{};
1420 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1421 rasterizer.depthClampEnable = VK_FALSE;
1422 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1423 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1424 rasterizer.lineWidth = 1.0f;
1425 rasterizer.cullMode = VK_CULL_MODE_NONE;
1426 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1427 rasterizer.depthBiasEnable = VK_FALSE;
1429 VkPipelineMultisampleStateCreateInfo multisampling{};
1430 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1431 multisampling.sampleShadingEnable = VK_FALSE;
1432 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1434 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1435 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1436 depthStencil.depthTestEnable = VK_FALSE;
1437 depthStencil.depthWriteEnable = VK_FALSE;
1439 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
1440 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
1441 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1442 colorBlendAttachment.blendEnable = VK_TRUE;
1443 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
1444 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1445 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
1446 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
1447 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1448 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
1450 VkPipelineColorBlendStateCreateInfo colorBlending{};
1451 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1452 colorBlending.logicOpEnable = VK_FALSE;
1453 colorBlending.attachmentCount = 1;
1454 colorBlending.pAttachments = &colorBlendAttachment;
1456 VkPushConstantRange pushConstantRange{};
1457 pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
1458 pushConstantRange.offset = 0;
1459 pushConstantRange.size =
sizeof(float) * 12;
1461 VkDescriptorSetLayout layoutToUseForPipeline = extendedUBOEnabled ? extendedDescriptorSetLayout : descriptorSetLayout;
1463 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
1464 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1465 pipelineLayoutInfo.setLayoutCount = 1;
1466 pipelineLayoutInfo.pSetLayouts = &layoutToUseForPipeline;
1467 pipelineLayoutInfo.pushConstantRangeCount = 1;
1468 pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
1470 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo,
nullptr, &customPipelineLayout));
1472 VkGraphicsPipelineCreateInfo pipelineInfo{};
1473 VkPipelineRenderingCreateInfo renderingInfo{};
1474 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1475 renderingInfo.viewMask = 0;
1476 renderingInfo.colorAttachmentCount = 1;
1477 renderingInfo.pColorAttachmentFormats = &colorAttachmentFormat;
1478 if (depthAttachmentFormat != VK_FORMAT_UNDEFINED) {
1479 renderingInfo.depthAttachmentFormat = depthAttachmentFormat;
1482 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1483 pipelineInfo.pNext = &renderingInfo;
1484 pipelineInfo.stageCount = 2;
1485 pipelineInfo.pStages = shaderStages;
1486 pipelineInfo.pVertexInputState = &vertexInputInfo;
1487 pipelineInfo.pInputAssemblyState = &inputAssembly;
1488 pipelineInfo.pViewportState = &viewportState;
1489 pipelineInfo.pRasterizationState = &rasterizer;
1490 pipelineInfo.pMultisampleState = &multisampling;
1491 pipelineInfo.pDepthStencilState = &depthStencil;
1492 pipelineInfo.pColorBlendState = &colorBlending;
1493 pipelineInfo.pDynamicState = &dynamicState;
1494 pipelineInfo.layout = customPipelineLayout;
1495 pipelineInfo.renderPass = VK_NULL_HANDLE;
1496 pipelineInfo.subpass = 0;
1497 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
1499 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo,
nullptr, &customPipeline));
1501 vkDestroyShaderModule(device, vertShaderModule,
nullptr);
1505 if (!hasCustomShader || fragmentShaderModule == VK_NULL_HANDLE)
1507 createCustomPipeline();
1508 std::cout <<
"mxvk: Pipeline rebuilt\n";
1512 if (path == fragmentShaderPath && fragmentShaderModule != VK_NULL_HANDLE) {
1516 if (customPipeline != VK_NULL_HANDLE) {
1517 vkDestroyPipeline(device, customPipeline,
nullptr);
1518 customPipeline = VK_NULL_HANDLE;
1520 if (customPipelineLayout != VK_NULL_HANDLE) {
1521 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
1522 customPipelineLayout = VK_NULL_HANDLE;
1524 if (fragmentShaderModule != VK_NULL_HANDLE) {
1525 vkDestroyShaderModule(device, fragmentShaderModule,
nullptr);
1526 fragmentShaderModule = VK_NULL_HANDLE;
1529 fragmentShaderPath = path;
1530 hasCustomShader =
false;
1532 if (fragmentShaderPath.empty()) {
1536 const auto shaderCode = readShaderFile(fragmentShaderPath);
1538 hasCustomShader =
true;
1540 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
1541 createCustomPipeline();
1545 void VK_Sprite::destroyComputePipeline() {
1546 if (computePipeline != VK_NULL_HANDLE) {
1547 std::cout <<
"vk: destroying sprite compute pipeline\n";
1548 vkDestroyPipeline(device, computePipeline,
nullptr);
1549 computePipeline = VK_NULL_HANDLE;
1551 if (computePipelineLayout != VK_NULL_HANDLE) {
1552 std::cout <<
"vk: destroying sprite compute pipeline layout\n";
1553 vkDestroyPipelineLayout(device, computePipelineLayout,
nullptr);
1554 computePipelineLayout = VK_NULL_HANDLE;
1558 void VK_Sprite::createComputePipeline() {
1559 if (computeShaderModule == VK_NULL_HANDLE ||
1560 extendedDescriptorSetLayout == VK_NULL_HANDLE) {
1564 destroyComputePipeline();
1565 VkPipelineLayoutCreateInfo layoutInfo{};
1566 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1567 layoutInfo.setLayoutCount = 1;
1568 layoutInfo.pSetLayouts = &extendedDescriptorSetLayout;
1570 device, &layoutInfo,
nullptr, &computePipelineLayout));
1572 VkPipelineShaderStageCreateInfo stageInfo{};
1573 stageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1574 stageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT;
1575 stageInfo.module = computeShaderModule;
1576 stageInfo.pName =
"main";
1578 VkComputePipelineCreateInfo pipelineInfo{};
1579 pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
1580 pipelineInfo.stage = stageInfo;
1581 pipelineInfo.layout = computePipelineLayout;
1583 device, pipelineCache, 1, &pipelineInfo,
nullptr,
1585 std::cout <<
"mxvk: Compute pipeline rebuilt\n";
1589 uint32_t localSizeX,
1590 uint32_t localSizeY,
1591 uint32_t localSizeZ) {
1592 if (path.empty() || localSizeX == 0 || localSizeY == 0 ||
1595 "VKSprite::enableComputeShader requires a shader and positive local size");
1598 vkDeviceWaitIdle(device);
1599 destroyComputePipeline();
1600 if (computeShaderModule != VK_NULL_HANDLE) {
1601 vkDestroyShaderModule(device, computeShaderModule,
nullptr);
1602 computeShaderModule = VK_NULL_HANDLE;
1604 computeShaderModule =
1606 computeLocalSizeX = localSizeX;
1607 computeLocalSizeY = localSizeY;
1609 if (!extendedUBOEnabled) {
1611 createComputePipeline();
1613 recreateExtendedDescriptorLayout();
1618 VkImageView inputView,
1619 VkImageView outputView, uint32_t width,
1621 if (computePipeline == VK_NULL_HANDLE ||
1622 computePipelineLayout == VK_NULL_HANDLE || inputView == VK_NULL_HANDLE ||
1623 outputView == VK_NULL_HANDLE || width == 0 || height == 0) {
1625 "VKSprite::dispatchCompute received an incomplete compute pass");
1629 static_cast<int>(height));
1630 if (computeOutputImageView != outputView) {
1631 if (extendedDescriptorPool != VK_NULL_HANDLE) {
1632 vkDeviceWaitIdle(device);
1633 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
1634 extendedDescriptorPool = VK_NULL_HANDLE;
1635 extendedDescriptorSet = VK_NULL_HANDLE;
1637 computeOutputImageView = outputView;
1639 updateExtendedUBO();
1640 if (extendedDescriptorSet == VK_NULL_HANDLE) {
1641 createExtendedDescriptorSet();
1643 if (extendedDescriptorSet == VK_NULL_HANDLE) {
1645 "VKSprite::dispatchCompute could not create its descriptor set");
1648 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE,
1650 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE,
1651 computePipelineLayout, 0, 1,
1652 &extendedDescriptorSet, 0,
nullptr);
1653 vkCmdDispatch(cmdBuffer,
1654 (width + computeLocalSizeX - 1U) / computeLocalSizeX,
1655 (height + computeLocalSizeY - 1U) / computeLocalSizeY,
1660 if (!instancingEnabled || instanceVertPath.empty() || instanceFragPath.empty())
1662 createInstancedPipeline(instanceVertPath, instanceFragPath);
1665 void VK_Sprite::createQuadBuffer() {
1666 if (quadBufferCreated)
1669 SpriteVertex vertices[] = {
1670 {{0.0f, 0.0f}, {0.0f, 0.0f}},
1671 {{1.0f, 0.0f}, {1.0f, 0.0f}},
1672 {{1.0f, 1.0f}, {1.0f, 1.0f}},
1673 {{0.0f, 1.0f}, {0.0f, 1.0f}}};
1674 uint16_t indices[] = {0, 1, 2, 0, 2, 3};
1676 VkDeviceSize vertexSize =
sizeof(vertices);
1677 createBuffer(vertexSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
1678 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1679 quadVertexBuffer, quadVertexBufferMemory);
1682 VK_CHECK_RESULT(vkMapMemory(device, quadVertexBufferMemory, 0, vertexSize, 0, &data));
1683 memcpy(data, vertices, vertexSize);
1684 vkUnmapMemory(device, quadVertexBufferMemory);
1686 VkDeviceSize indexSize =
sizeof(indices);
1687 createBuffer(indexSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
1688 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1689 quadIndexBuffer, quadIndexBufferMemory);
1691 VK_CHECK_RESULT(vkMapMemory(device, quadIndexBufferMemory, 0, indexSize, 0, &data));
1692 memcpy(data, indices, indexSize);
1693 vkUnmapMemory(device, quadIndexBufferMemory);
1695 quadBufferCreated =
true;
1705 std::cout << std::format(
"mxvk: Loaded PNG: {}\n", pngPath);
1710 throw mxvk::Exception(
"VKSprite::loadSprite called with null surface");
1712 if (spriteLoaded || spriteImage != VK_NULL_HANDLE || fragmentShaderModule != VK_NULL_HANDLE) {
1713 destroySpriteResources();
1715 SDL_Surface *rgbaSurface = convertToRGBA(
surface);
1719 spriteWidth = rgbaSurface->w;
1720 spriteHeight = rgbaSurface->h;
1721 createSpriteTexture(rgbaSurface);
1722 SDL_DestroySurface(rgbaSurface);
1725 if (!fragmentShaderPath.empty()) {
1726 auto shaderCode = readShaderFile(fragmentShaderPath);
1728 hasCustomShader =
true;
1729 this->fragmentShaderPath = fragmentShaderPath;
1731 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
1732 createCustomPipeline();
1735 spriteLoaded =
true;
1736 std::cout << std::format(
"mxvk: Loaded surface texture: {}x{}\n", spriteWidth, spriteHeight);
1740 if (width <= 0 || height <= 0) {
1741 throw mxvk::Exception(
"VKSprite::createEmptySprite invalid dimensions");
1743 if (spriteLoaded || spriteImage != VK_NULL_HANDLE || fragmentShaderModule != VK_NULL_HANDLE) {
1744 destroySpriteResources();
1746 spriteWidth = width;
1747 spriteHeight = height;
1749 if (!vertexShaderPath.empty()) {
1755 createCudaExportableImage(width, height, 1, spriteImage,
1757 cudaExportMemorySize);
1758 cudaInteropUnavailableLogged =
false;
1759 }
catch (
const std::exception &ex) {
1760 std::cout << std::format(
"mxvk: CUDA exportable sprite image unavailable: {}; using standard Vulkan image\n", ex.what());
1761 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
1762 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1763 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
1766 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
1767 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1768 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
1771 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1772 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
1773 VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) * height * 4;
1775 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
1776 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1777 stagingBuffer, stagingMemory);
1780 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
1781 memset(data, 0, imageSize);
1782 vkUnmapMemory(device, stagingMemory);
1784 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1785 copyBufferToImage(stagingBuffer, spriteImage, width, height);
1786 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1788 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1791 vkDestroyBuffer(device, stagingBuffer,
nullptr);
1792 vkFreeMemory(device, stagingMemory,
nullptr);
1794 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
1797 createDescriptorPool();
1799 createStagingResources(imageSize);
1801 if (!fragmentShaderPath.empty()) {
1802 auto shaderCode = readShaderFile(fragmentShaderPath);
1804 hasCustomShader =
true;
1805 this->fragmentShaderPath = fragmentShaderPath;
1807 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
1808 createCustomPipeline();
1812 spriteLoaded =
true;
1813 std::cout << std::format(
"mxvk: Created empty sprite: {}x{}\n", spriteWidth, spriteHeight);
1818 throw mxvk::Exception(
"VKSprite::updateTexture called with null surface");
1820 if (!spriteLoaded) {
1821 throw mxvk::Exception(
"VKSprite::updateTexture called before sprite was loaded");
1823 SDL_Surface *rgbaSurface = convertToRGBA(
surface);
1825 throw mxvk::Exception(
"Failed to convert surface to RGBA in updateTexture");
1827 if (rgbaSurface->w == spriteWidth && rgbaSurface->h == spriteHeight) {
1829 if (updateTextureCudaHost(rgbaSurface->pixels,
static_cast<uint32_t
>(rgbaSurface->w),
static_cast<uint32_t
>(rgbaSurface->h),
1830 static_cast<uint32_t
>(rgbaSurface->pitch))) {
1831 SDL_DestroySurface(rgbaSurface);
1835 updateSpriteTexture(rgbaSurface->pixels, rgbaSurface->w, rgbaSurface->h);
1837 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
1838 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1841 destroyCudaInterop();
1843 destroyTextureDescriptorPools();
1844 if (spriteImageView != VK_NULL_HANDLE) {
1845 vkDestroyImageView(device, spriteImageView,
nullptr);
1846 spriteImageView = VK_NULL_HANDLE;
1848 if (spriteImage != VK_NULL_HANDLE) {
1849 vkDestroyImage(device, spriteImage,
nullptr);
1850 spriteImage = VK_NULL_HANDLE;
1852 if (spriteImageMemory != VK_NULL_HANDLE) {
1853 vkFreeMemory(device, spriteImageMemory,
nullptr);
1854 spriteImageMemory = VK_NULL_HANDLE;
1856 spriteWidth = rgbaSurface->w;
1857 spriteHeight = rgbaSurface->h;
1858 createSpriteTexture(rgbaSurface);
1859 createDescriptorPool();
1861 SDL_DestroySurface(rgbaSurface);
1866 throw mxvk::Exception(
"VKSprite::updateTexture called with null pixel data");
1868 if (!spriteLoaded) {
1869 throw mxvk::Exception(
"VKSprite::updateTexture called before sprite was loaded");
1871 if (width <= 0 || height <= 0) {
1874 int srcPitch = (pitch > 0) ? pitch : width * 4;
1875 if (width == spriteWidth && height == spriteHeight && srcPitch == width * 4) {
1877 if (updateTextureCudaHost(pixels,
static_cast<uint32_t
>(width),
static_cast<uint32_t
>(height),
static_cast<uint32_t
>(srcPitch))) {
1881 updateSpriteTexture(pixels, width, height);
1882 }
else if (width == spriteWidth && height == spriteHeight) {
1884 if (updateTextureCudaHost(pixels,
static_cast<uint32_t
>(width),
static_cast<uint32_t
>(height),
static_cast<uint32_t
>(srcPitch))) {
1888 std::vector<uint8_t> packed(width * height * 4);
1889 const uint8_t *src =
static_cast<const uint8_t *
>(pixels);
1890 for (
int row = 0; row < height; ++row) {
1891 memcpy(packed.data() + row * width * 4, src + row * srcPitch, width * 4);
1893 updateSpriteTexture(packed.data(), width, height);
1895 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
1896 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1899 destroyCudaInterop();
1901 destroyTextureDescriptorPools();
1902 if (spriteImageView != VK_NULL_HANDLE) {
1903 vkDestroyImageView(device, spriteImageView,
nullptr);
1904 spriteImageView = VK_NULL_HANDLE;
1906 if (spriteImage != VK_NULL_HANDLE) {
1907 vkDestroyImage(device, spriteImage,
nullptr);
1908 spriteImage = VK_NULL_HANDLE;
1910 if (spriteImageMemory != VK_NULL_HANDLE) {
1911 vkFreeMemory(device, spriteImageMemory,
nullptr);
1912 spriteImageMemory = VK_NULL_HANDLE;
1914 spriteWidth = width;
1915 spriteHeight = height;
1916 std::vector<uint8_t> packed;
1917 const void *texData = pixels;
1918 if (srcPitch != width * 4) {
1919 packed.resize(width * height * 4);
1920 const uint8_t *src =
static_cast<const uint8_t *
>(pixels);
1921 for (
int row = 0; row < height; ++row) {
1922 memcpy(packed.data() + row * width * 4, src + row * srcPitch, width * 4);
1924 texData = packed.data();
1927 SDL_Surface *tmpSurface = SDL_CreateSurfaceFrom(
1928 width, height, SDL_PIXELFORMAT_RGBA32,
1929 const_cast<void *
>(texData), width * 4);
1931 throw mxvk::Exception(
"VKSprite::updateTexture failed to create temp surface");
1933 createSpriteTexture(tmpSurface);
1934 SDL_DestroySurface(tmpSurface);
1935 createDescriptorPool();
1939 void VK_Sprite::updateSpriteTexture(
const void *pixels, uint32_t width, uint32_t height) {
1940 VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) * height * 4;
1942 createStagingResources(imageSize);
1943 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
1945 memcpy(persistentStagingMapped, pixels, imageSize);
1947 VkCommandBufferBeginInfo beginInfo{};
1948 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1949 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1951 VkImageMemoryBarrier barrier{};
1952 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1953 VkImageLayout oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1955 if (cudaImageLayout != VK_IMAGE_LAYOUT_UNDEFINED) {
1956 oldLayout = cudaImageLayout;
1959 barrier.oldLayout = oldLayout;
1960 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
1961 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1962 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1963 barrier.image = spriteImage;
1964 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1965 barrier.subresourceRange.baseMipLevel = 0;
1966 barrier.subresourceRange.levelCount = 1;
1967 barrier.subresourceRange.baseArrayLayer = 0;
1968 barrier.subresourceRange.layerCount = 1;
1969 barrier.srcAccessMask = (oldLayout == VK_IMAGE_LAYOUT_GENERAL) ? VK_ACCESS_MEMORY_WRITE_BIT : VK_ACCESS_SHADER_READ_BIT;
1970 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1971 const VkPipelineStageFlags srcStage = (oldLayout == VK_IMAGE_LAYOUT_GENERAL)
1972 ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT
1973 : VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1974 vkCmdPipelineBarrier(uploadCmdBuffer, srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT,
1975 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1977 VkBufferImageCopy region{};
1978 region.bufferOffset = 0;
1979 region.bufferRowLength = 0;
1980 region.bufferImageHeight = 0;
1981 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1982 region.imageSubresource.mipLevel = 0;
1983 region.imageSubresource.baseArrayLayer = 0;
1984 region.imageSubresource.layerCount = 1;
1985 region.imageOffset = {0, 0, 0};
1986 region.imageExtent = {width, height, 1};
1987 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer, spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
1989 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
1990 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1991 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1992 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1993 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
1994 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1998 VkSubmitInfo submitInfo{};
1999 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2000 submitInfo.commandBufferCount = 1;
2001 submitInfo.pCommandBuffers = &uploadCmdBuffer;
2002 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
2004 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2005 cudaImageNeedsShaderBarrier =
false;
2010 void VK_Sprite::destroyCudaInterop() {
2011 if (cudaInteropEnabled || cudaExternalMemory !=
nullptr || cudaMipmappedArray !=
nullptr) {
2012 std::cout <<
"mxvk: CUDA interop: destroying imported Vulkan texture resources\n";
2014 if (cudaMipmappedArray !=
nullptr) {
2015 cudaFreeMipmappedArray(cudaMipmappedArray);
2016 cudaMipmappedArray =
nullptr;
2017 cudaArray =
nullptr;
2019 if (cudaExternalMemory !=
nullptr) {
2020 cudaDestroyExternalMemory(cudaExternalMemory);
2021 cudaExternalMemory =
nullptr;
2023 cudaInteropEnabled =
false;
2024 cudaImageNeedsShaderBarrier =
false;
2025 cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2026 cudaExportMemorySize = 0;
2027 cudaUploadLogged =
false;
2028 cudaWriteTransitionLogged =
false;
2029 cudaSampleBarrierLogged =
false;
2032 void VK_Sprite::createCudaExportableImage(
2033 uint32_t width, uint32_t height, uint32_t arrayLayers, VkImage &image,
2034 VkDeviceMemory &imageMemory, VkDeviceSize &exportMemorySize) {
2035 std::cout << std::format(
2036 "mxvk: CUDA interop init: requesting exportable Vulkan image "
2037 "{}x{}x{} RGBA8 OPAQUE_FD\n",
2038 width, height, arrayLayers);
2039 if (image != VK_NULL_HANDLE) {
2040 vkDestroyImage(device, image,
nullptr);
2041 image = VK_NULL_HANDLE;
2043 if (imageMemory != VK_NULL_HANDLE) {
2044 vkFreeMemory(device, imageMemory,
nullptr);
2045 imageMemory = VK_NULL_HANDLE;
2048 VkExternalMemoryImageCreateInfo externalImageInfo{};
2049 externalImageInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
2050 externalImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
2052 VkImageCreateInfo imageInfo{};
2053 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2054 imageInfo.pNext = &externalImageInfo;
2055 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2056 imageInfo.extent.width = width;
2057 imageInfo.extent.height = height;
2058 imageInfo.extent.depth = 1;
2059 imageInfo.mipLevels = 1;
2060 imageInfo.arrayLayers = arrayLayers;
2061 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
2062 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
2063 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2064 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
2065 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2066 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2070 VkMemoryRequirements memRequirements{};
2071 vkGetImageMemoryRequirements(device, image, &memRequirements);
2073 VkExportMemoryAllocateInfo exportMemoryInfo{};
2074 exportMemoryInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
2075 exportMemoryInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
2077 VkMemoryAllocateInfo allocInfo{};
2078 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
2079 allocInfo.pNext = &exportMemoryInfo;
2080 allocInfo.allocationSize = memRequirements.size;
2083 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
2084 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &imageMemory));
2086 exportMemorySize = memRequirements.size;
2087 std::cout << std::format(
2088 "mxvk: CUDA interop init: exportable Vulkan image allocated (memorySize={} bytes, memoryType={})\n",
2089 static_cast<unsigned long long>(exportMemorySize),
2090 allocInfo.memoryTypeIndex);
2092 if (imageMemory != VK_NULL_HANDLE) {
2093 vkFreeMemory(device, imageMemory,
nullptr);
2094 imageMemory = VK_NULL_HANDLE;
2096 if (image != VK_NULL_HANDLE) {
2097 vkDestroyImage(device, image,
nullptr);
2098 image = VK_NULL_HANDLE;
2100 exportMemorySize = 0;
2105 bool VK_Sprite::ensureCudaInterop() {
2106 if (cudaInteropEnabled) {
2109 if (spriteImage == VK_NULL_HANDLE || spriteImageMemory == VK_NULL_HANDLE || cudaExportMemorySize == 0) {
2110 if (!cudaInteropUnavailableLogged) {
2111 std::cout <<
"mxvk: CUDA interop init: sprite image is not exportable; using CPU/pinned fallback\n";
2112 cudaInteropUnavailableLogged =
true;
2116 if (vkGetMemoryFdKHR ==
nullptr) {
2117 if (!cudaInteropUnavailableLogged) {
2118 std::cout <<
"mxvk: CUDA interop init: vkGetMemoryFdKHR was not loaded; using CPU/pinned fallback\n";
2119 cudaInteropUnavailableLogged =
true;
2124 VkMemoryGetFdInfoKHR fdInfo{};
2125 fdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
2126 fdInfo.memory = spriteImageMemory;
2127 fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
2130 const VkResult fdResult = vkGetMemoryFdKHR(device, &fdInfo, &memoryFd);
2131 if (fdResult != VK_SUCCESS) {
2132 if (!cudaInteropUnavailableLogged) {
2133 std::cout << std::format(
"mxvk: CUDA interop init: vkGetMemoryFdKHR failed ({})\n",
static_cast<int>(fdResult));
2134 cudaInteropUnavailableLogged =
true;
2138 std::cout << std::format(
"mxvk: CUDA interop init: exported Vulkan image memory fd={}\n", memoryFd);
2140 cudaExternalMemoryHandleDesc externalMemoryDesc{};
2141 externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueFd;
2142 externalMemoryDesc.handle.fd = memoryFd;
2143 externalMemoryDesc.size = cudaExportMemorySize;
2145 cudaError_t cudaResult = cudaImportExternalMemory(&cudaExternalMemory, &externalMemoryDesc);
2146 if (cudaResult != cudaSuccess) {
2148 if (!cudaInteropUnavailableLogged) {
2149 std::cout << std::format(
"mxvk: CUDA interop init: cudaImportExternalMemory failed: {}\n",
2150 cudaGetErrorString(cudaResult));
2151 cudaInteropUnavailableLogged =
true;
2153 cudaExternalMemory =
nullptr;
2156 std::cout << std::format(
"mxvk: CUDA interop init: imported external memory into CUDA ({} bytes)\n",
2157 static_cast<unsigned long long>(cudaExportMemorySize));
2159 cudaExternalMemoryMipmappedArrayDesc arrayDesc{};
2160 arrayDesc.offset = 0;
2161 arrayDesc.formatDesc = cudaCreateChannelDesc<uchar4>();
2162 arrayDesc.extent = make_cudaExtent(
static_cast<size_t>(spriteWidth),
static_cast<size_t>(spriteHeight), 0);
2163 arrayDesc.flags = cudaArrayColorAttachment;
2164 arrayDesc.numLevels = 1;
2166 cudaResult = cudaExternalMemoryGetMappedMipmappedArray(&cudaMipmappedArray, cudaExternalMemory, &arrayDesc);
2167 if (cudaResult != cudaSuccess) {
2168 if (!cudaInteropUnavailableLogged) {
2169 std::cout << std::format(
"mxvk: CUDA interop init: cudaExternalMemoryGetMappedMipmappedArray failed: {}\n",
2170 cudaGetErrorString(cudaResult));
2171 cudaInteropUnavailableLogged =
true;
2173 destroyCudaInterop();
2176 std::cout << std::format(
"mxvk: CUDA interop init: mapped CUDA mipmapped array {}x{} uchar4\n", spriteWidth, spriteHeight);
2178 cudaResult = cudaGetMipmappedArrayLevel(&cudaArray, cudaMipmappedArray, 0);
2179 if (cudaResult != cudaSuccess) {
2180 if (!cudaInteropUnavailableLogged) {
2181 std::cout << std::format(
"mxvk: CUDA interop init: cudaGetMipmappedArrayLevel failed: {}\n",
2182 cudaGetErrorString(cudaResult));
2183 cudaInteropUnavailableLogged =
true;
2185 destroyCudaInterop();
2189 cudaInteropEnabled =
true;
2190 std::cout <<
"mxvk: CUDA interop init: direct CUDA-to-Vulkan texture upload is ready\n";
2194 void VK_Sprite::destroyCudaHistoryInterop() {
2195 if (cudaHistoryInteropEnabled ||
2196 cudaHistoryExternalMemory !=
nullptr ||
2197 cudaHistoryMipmappedArray !=
nullptr) {
2198 std::cout <<
"mxvk: CUDA interop: destroying imported history "
2199 "texture resources\n";
2201 if (cudaHistoryMipmappedArray !=
nullptr) {
2202 cudaFreeMipmappedArray(cudaHistoryMipmappedArray);
2203 cudaHistoryMipmappedArray =
nullptr;
2204 cudaHistoryArray =
nullptr;
2206 if (cudaHistoryExternalMemory !=
nullptr) {
2207 cudaDestroyExternalMemory(cudaHistoryExternalMemory);
2208 cudaHistoryExternalMemory =
nullptr;
2210 cudaHistoryInteropEnabled =
false;
2211 cudaHistoryExportMemorySize = 0;
2212 cudaHistoryUploadLogged =
false;
2215 bool VK_Sprite::ensureCudaHistoryInterop() {
2216 if (cudaHistoryInteropEnabled) {
2219 if (historyImage == VK_NULL_HANDLE ||
2220 historyImageMemory == VK_NULL_HANDLE ||
2221 cudaHistoryExportMemorySize == 0) {
2222 if (!cudaHistoryInteropUnavailableLogged) {
2223 std::cout <<
"mxvk: CUDA history interop: history image is not "
2225 cudaHistoryInteropUnavailableLogged =
true;
2229 if (vkGetMemoryFdKHR ==
nullptr) {
2230 if (!cudaHistoryInteropUnavailableLogged) {
2231 std::cout <<
"mxvk: CUDA history interop: vkGetMemoryFdKHR was "
2233 cudaHistoryInteropUnavailableLogged =
true;
2238 VkMemoryGetFdInfoKHR fdInfo{};
2239 fdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
2240 fdInfo.memory = historyImageMemory;
2241 fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
2244 const VkResult fdResult =
2245 vkGetMemoryFdKHR(device, &fdInfo, &memoryFd);
2246 if (fdResult != VK_SUCCESS) {
2247 if (!cudaHistoryInteropUnavailableLogged) {
2248 std::cout << std::format(
2249 "mxvk: CUDA history interop: vkGetMemoryFdKHR failed "
2251 static_cast<int>(fdResult));
2252 cudaHistoryInteropUnavailableLogged =
true;
2257 cudaExternalMemoryHandleDesc externalMemoryDesc{};
2258 externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueFd;
2259 externalMemoryDesc.handle.fd = memoryFd;
2260 externalMemoryDesc.size = cudaHistoryExportMemorySize;
2262 cudaError_t cudaResult = cudaImportExternalMemory(
2263 &cudaHistoryExternalMemory, &externalMemoryDesc);
2264 if (cudaResult != cudaSuccess) {
2266 if (!cudaHistoryInteropUnavailableLogged) {
2267 std::cout << std::format(
2268 "mxvk: CUDA history interop: import failed: {}\n",
2269 cudaGetErrorString(cudaResult));
2270 cudaHistoryInteropUnavailableLogged =
true;
2272 cudaHistoryExternalMemory =
nullptr;
2276 cudaExternalMemoryMipmappedArrayDesc arrayDesc{};
2277 arrayDesc.offset = 0;
2278 arrayDesc.formatDesc = cudaCreateChannelDesc<uchar4>();
2279 arrayDesc.extent = make_cudaExtent(
2280 static_cast<size_t>(historyWidth),
2281 static_cast<size_t>(historyHeight),
2282 static_cast<size_t>(historyLayers));
2283 arrayDesc.flags = cudaArrayColorAttachment | cudaArrayLayered;
2284 arrayDesc.numLevels = 1;
2286 cudaResult = cudaExternalMemoryGetMappedMipmappedArray(
2287 &cudaHistoryMipmappedArray, cudaHistoryExternalMemory, &arrayDesc);
2288 if (cudaResult != cudaSuccess) {
2289 if (!cudaHistoryInteropUnavailableLogged) {
2290 std::cout << std::format(
2291 "mxvk: CUDA history interop: array mapping failed: {}\n",
2292 cudaGetErrorString(cudaResult));
2293 cudaHistoryInteropUnavailableLogged =
true;
2295 destroyCudaHistoryInterop();
2299 cudaResult = cudaGetMipmappedArrayLevel(
2300 &cudaHistoryArray, cudaHistoryMipmappedArray, 0);
2301 if (cudaResult != cudaSuccess) {
2302 if (!cudaHistoryInteropUnavailableLogged) {
2303 std::cout << std::format(
2304 "mxvk: CUDA history interop: array lookup failed: {}\n",
2305 cudaGetErrorString(cudaResult));
2306 cudaHistoryInteropUnavailableLogged =
true;
2308 destroyCudaHistoryInterop();
2312 cudaHistoryInteropEnabled =
true;
2313 cudaHistoryInteropUnavailableLogged =
false;
2314 std::cout << std::format(
2315 "mxvk: CUDA history interop: direct {}-layer upload is ready\n",
2320 void VK_Sprite::transitionCudaHistoryLayer(
2321 VkImageLayout oldLayout, VkImageLayout newLayout,
2322 VkAccessFlags sourceAccess, VkAccessFlags destinationAccess,
2323 VkPipelineStageFlags sourceStage,
2324 VkPipelineStageFlags destinationStage) {
2325 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2326 VkImageMemoryBarrier barrier{};
2327 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2328 barrier.oldLayout = oldLayout;
2329 barrier.newLayout = newLayout;
2330 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2331 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2332 barrier.image = historyImage;
2333 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2334 barrier.subresourceRange.baseMipLevel = 0;
2335 barrier.subresourceRange.levelCount = 1;
2336 barrier.subresourceRange.baseArrayLayer = historyHead;
2337 barrier.subresourceRange.layerCount = 1;
2338 barrier.srcAccessMask = sourceAccess;
2339 barrier.dstAccessMask = destinationAccess;
2340 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
2341 nullptr, 0,
nullptr, 1, &barrier);
2342 endSingleTimeCommands(commandBuffer);
2345 bool VK_Sprite::transitionCudaImageForWrite() {
2346 if (cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) {
2350 const VkImageLayout oldLayout = (cudaImageLayout == VK_IMAGE_LAYOUT_UNDEFINED)
2351 ? VK_IMAGE_LAYOUT_UNDEFINED
2353 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2355 VkImageMemoryBarrier barrier{};
2356 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2357 barrier.oldLayout = oldLayout;
2358 barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
2359 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2360 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2361 barrier.image = spriteImage;
2362 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2363 barrier.subresourceRange.baseMipLevel = 0;
2364 barrier.subresourceRange.levelCount = 1;
2365 barrier.subresourceRange.baseArrayLayer = 0;
2366 barrier.subresourceRange.layerCount = 1;
2367 barrier.srcAccessMask = (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) ? VK_ACCESS_SHADER_READ_BIT : 0;
2368 barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
2370 const VkPipelineStageFlags srcStage = (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
2371 ? VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
2372 : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2373 vkCmdPipelineBarrier(commandBuffer, srcStage, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
2374 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
2375 endSingleTimeCommands(commandBuffer);
2377 cudaImageLayout = VK_IMAGE_LAYOUT_GENERAL;
2378 if (!cudaWriteTransitionLogged) {
2379 std::cout <<
"mxvk: CUDA interop sync: Vulkan image transitions to GENERAL before CUDA writes\n";
2380 cudaWriteTransitionLogged =
true;
2385 bool VK_Sprite::transitionCudaImageForShaderRead() {
2386 if (cudaImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && !cudaImageNeedsShaderBarrier) {
2390 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2391 VkImageMemoryBarrier barrier{};
2392 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2393 barrier.oldLayout = cudaImageLayout;
2394 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2395 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2396 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2397 barrier.image = spriteImage;
2398 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2399 barrier.subresourceRange.baseMipLevel = 0;
2400 barrier.subresourceRange.levelCount = 1;
2401 barrier.subresourceRange.baseArrayLayer = 0;
2402 barrier.subresourceRange.layerCount = 1;
2403 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
2404 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
2406 vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
2407 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
2408 endSingleTimeCommands(commandBuffer);
2410 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2411 cudaImageNeedsShaderBarrier =
false;
2412 if (!cudaSampleBarrierLogged) {
2413 std::cout <<
"mxvk: CUDA interop sync: Vulkan transitions GENERAL -> SHADER_READ_ONLY before sampling\n";
2414 cudaSampleBarrierLogged =
true;
2419 void VK_Sprite::recordCudaReadyBarrier(VkCommandBuffer cmdBuffer) {
2420 if (!cudaImageNeedsShaderBarrier) {
2425 transitionCudaImageForShaderRead();
2428 bool VK_Sprite::updateTextureCuda(
const cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream) {
2429 if (!spriteLoaded) {
2432 if (rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols <= 0 || rgba.rows <= 0) {
2435 if (rgba.cols != spriteWidth || rgba.rows != spriteHeight || spriteImage == VK_NULL_HANDLE ||
2436 spriteImageMemory == VK_NULL_HANDLE || spriteImageView == VK_NULL_HANDLE || cudaExportMemorySize == 0) {
2437 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
2438 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
2440 vkDeviceWaitIdle(device);
2441 destroyCudaInterop();
2442 destroyTextureDescriptorPools();
2443 if (spriteImageView != VK_NULL_HANDLE) {
2444 vkDestroyImageView(device, spriteImageView,
nullptr);
2445 spriteImageView = VK_NULL_HANDLE;
2447 if (spriteImage != VK_NULL_HANDLE) {
2448 vkDestroyImage(device, spriteImage,
nullptr);
2449 spriteImage = VK_NULL_HANDLE;
2451 if (spriteImageMemory != VK_NULL_HANDLE) {
2452 vkFreeMemory(device, spriteImageMemory,
nullptr);
2453 spriteImageMemory = VK_NULL_HANDLE;
2456 spriteWidth = rgba.cols;
2457 spriteHeight = rgba.rows;
2459 createCudaExportableImage(
2460 static_cast<uint32_t
>(spriteWidth),
2461 static_cast<uint32_t
>(spriteHeight), 1, spriteImage,
2462 spriteImageMemory, cudaExportMemorySize);
2463 cudaInteropUnavailableLogged =
false;
2464 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
2465 }
catch (
const std::exception &ex) {
2466 if (!cudaInteropUnavailableLogged) {
2467 std::cout << std::format(
"mxvk: CUDA exportable sprite resize unavailable: {}; using CPU/pinned fallback\n", ex.what());
2468 cudaInteropUnavailableLogged =
true;
2473 createDescriptorPool();
2479 if (!ensureCudaInterop() || !transitionCudaImageForWrite()) {
2483 cudaStream_t cudaStream = cuda_stream_handle(stream);
2484 if (!cudaUploadLogged) {
2485 std::cout << std::format(
"mxvk: CUDA interop upload: copying {}x{} RGBA GpuMat to Vulkan image array (pitch={} bytes)\n",
2486 rgba.cols, rgba.rows,
static_cast<unsigned long long>(rgba.step));
2487 cudaUploadLogged =
true;
2489 cudaError_t cudaResult = cudaMemcpy2DToArrayAsync(
2490 cudaArray, 0, 0, rgba.ptr(), rgba.step,
2491 static_cast<size_t>(rgba.cols) * 4,
static_cast<size_t>(rgba.rows),
2492 cudaMemcpyDeviceToDevice, cudaStream);
2493 if (cudaResult != cudaSuccess) {
2494 std::cout << std::format(
"mxvk: CUDA interop texture copy failed: {}\n", cudaGetErrorString(cudaResult));
2498 cudaResult = cudaStreamSynchronize(cudaStream);
2499 if (cudaResult != cudaSuccess) {
2500 std::cout << std::format(
"mxvk: CUDA interop texture sync failed: {}\n", cudaGetErrorString(cudaResult));
2504 cudaImageNeedsShaderBarrier =
true;
2505 return transitionCudaImageForShaderRead();
2508 bool VK_Sprite::updateHistoryTextureCuda(
const cv::cuda::GpuMat &rgba,
2509 cv::cuda::Stream &stream) {
2510 if (!historyTextureEnabled || rgba.empty() || rgba.type() != CV_8UC4 ||
2511 rgba.cols <= 0 || rgba.rows <= 0 ||
2512 static_cast<uint32_t
>(rgba.cols) != historyWidth ||
2513 static_cast<uint32_t
>(rgba.rows) != historyHeight ||
2514 !ensureCudaHistoryInterop()) {
2518 transitionCudaHistoryLayer(
2519 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL,
2520 VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_MEMORY_WRITE_BIT,
2521 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
2522 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
2524 cudaMemcpy3DParms copyParameters{};
2525 copyParameters.srcPtr = make_cudaPitchedPtr(
2526 const_cast<unsigned char *
>(rgba.ptr()), rgba.step,
2527 static_cast<size_t>(rgba.cols),
2528 static_cast<size_t>(rgba.rows));
2529 copyParameters.dstArray = cudaHistoryArray;
2530 copyParameters.dstPos = make_cudaPos(0, 0, historyHead);
2531 copyParameters.extent = make_cudaExtent(
2532 static_cast<size_t>(rgba.cols),
static_cast<size_t>(rgba.rows), 1);
2533 copyParameters.kind = cudaMemcpyDeviceToDevice;
2535 cudaStream_t cudaStream = cuda_stream_handle(stream);
2536 cudaError_t cudaResult =
2537 cudaMemcpy3DAsync(©Parameters, cudaStream);
2538 if (cudaResult == cudaSuccess) {
2539 cudaResult = cudaStreamSynchronize(cudaStream);
2542 transitionCudaHistoryLayer(
2543 VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
2544 VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
2545 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
2546 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
2548 if (cudaResult != cudaSuccess) {
2549 std::cout << std::format(
2550 "mxvk: CUDA history interop upload failed: {}\n",
2551 cudaGetErrorString(cudaResult));
2554 if (!cudaHistoryUploadLogged) {
2555 std::cout << std::format(
2556 "mxvk: CUDA history interop: copying {}x{} RGBA GpuMat into "
2557 "the Vulkan history array\n",
2558 rgba.cols, rgba.rows);
2559 cudaHistoryUploadLogged =
true;
2561 historyHead = (historyHead + 1) % historyLayers;
2565 bool VK_Sprite::updateTextureCudaHost(
const void *pixels, uint32_t width, uint32_t height, uint32_t pitch) {
2566 if (pixels ==
nullptr || width == 0 || height == 0) {
2569 const uint32_t rowBytes = width * 4U;
2570 if (pitch < rowBytes ||
static_cast<int>(width) != spriteWidth ||
static_cast<int>(height) != spriteHeight) {
2573 if (!ensureCudaInterop() || !transitionCudaImageForWrite()) {
2577 if (!cudaUploadLogged) {
2578 std::cout << std::format(
2579 "mxvk: CUDA interop upload: copying {}x{} host RGBA pixels to Vulkan image array (pitch={} bytes)\n",
2580 width, height, pitch);
2581 cudaUploadLogged =
true;
2584 const cudaError_t copyResult = cudaMemcpy2DToArray(
2585 cudaArray, 0, 0, pixels, pitch,
2586 static_cast<size_t>(rowBytes),
static_cast<size_t>(height),
2587 cudaMemcpyHostToDevice);
2588 if (copyResult != cudaSuccess) {
2589 std::cout << std::format(
"mxvk: CUDA interop host texture copy failed: {}\n", cudaGetErrorString(copyResult));
2593 cudaImageNeedsShaderBarrier =
true;
2594 return transitionCudaImageForShaderRead();
2598 void VK_Sprite::createSpriteTexture(SDL_Surface *surface) {
2601 createCudaExportableImage(surface->w, surface->h, 1, spriteImage,
2603 cudaExportMemorySize);
2604 cudaInteropUnavailableLogged =
false;
2605 }
catch (
const std::exception &ex) {
2606 std::cout << std::format(
"mxvk: CUDA exportable sprite image unavailable: {}; using standard Vulkan image\n", ex.what());
2607 createImage(surface->w, surface->h, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
2608 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
2609 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
2612 createImage(surface->w, surface->h, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
2613 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
2614 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
2618 if (updateTextureCudaHost(surface->pixels,
static_cast<uint32_t
>(surface->w),
static_cast<uint32_t
>(surface->h),
2619 static_cast<uint32_t
>(surface->pitch))) {
2620 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
2625 VkBuffer stagingBuffer = VK_NULL_HANDLE;
2626 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
2627 VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(surface->w) * surface->h * 4;
2629 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
2630 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2631 stagingBuffer, stagingMemory);
2634 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
2635 const int rowBytes = surface->w * 4;
2636 if (surface->pitch == rowBytes) {
2637 memcpy(data, surface->pixels, imageSize);
2639 const auto *src =
static_cast<const uint8_t *
>(surface->pixels);
2640 auto *dst =
static_cast<uint8_t *
>(data);
2641 for (
int y = 0; y < surface->h; ++y)
2642 memcpy(dst + y * rowBytes, src + y * surface->pitch, rowBytes);
2644 vkUnmapMemory(device, stagingMemory);
2647 const VkImageLayout uploadOldLayout = (cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL)
2648 ? VK_IMAGE_LAYOUT_GENERAL
2649 : VK_IMAGE_LAYOUT_UNDEFINED;
2651 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2653 transitionImageLayout(spriteImage, uploadOldLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
2654 copyBufferToImage(stagingBuffer, spriteImage, surface->w, surface->h);
2655 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
2657 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2660 vkDestroyBuffer(device, stagingBuffer,
nullptr);
2661 vkFreeMemory(device, stagingMemory,
nullptr);
2663 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
2666 void VK_Sprite::createSampler() {
2671 VkSamplerCreateInfo samplerInfo{};
2672 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
2673 samplerInfo.magFilter = textureFilter;
2674 samplerInfo.minFilter = textureFilter;
2675 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
2676 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
2677 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
2678 samplerInfo.anisotropyEnable = VK_FALSE;
2679 samplerInfo.borderColor = VK_BORDER_COLOR_INT_TRANSPARENT_BLACK;
2680 samplerInfo.unnormalizedCoordinates = VK_FALSE;
2681 samplerInfo.compareEnable = VK_FALSE;
2682 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
2683 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
2684 samplerInfo.minLod = 0.0f;
2685 samplerInfo.maxLod = 0.0f;
2686 samplerInfo.mipLodBias = 0.0f;
2696 drawSpriteRect(x, y,
static_cast<int>(spriteWidth * scaleX),
static_cast<int>(spriteHeight * scaleY));
2700 if (!spriteLoaded) {
2701 throw mxvk::Exception(
"VKSprite::drawSprite called before sprite was loaded");
2704 drawQueue.push_back({
static_cast<float>(x),
static_cast<float>(y),
2705 static_cast<float>(
static_cast<int>(spriteWidth * scaleX)),
2706 static_cast<float>(
static_cast<int>(spriteHeight * scaleY)),
2707 rotation, shaderParams});
2711 if (!spriteLoaded) {
2712 throw mxvk::Exception(
"VKSprite::drawSpriteRect called before sprite was loaded");
2715 drawQueue.push_back({
static_cast<float>(x),
static_cast<float>(y),
2716 static_cast<float>(w),
static_cast<float>(h), 0.0f, shaderParams});
2720 shaderParams = glm::vec4(p1, p2, p3, p4);
2724 if (image_view == VK_NULL_HANDLE || width <= 0 || height <= 0) {
2725 throw mxvk::Exception(
"VKSprite::setExternalTexture received an invalid image view");
2727 if (externalTexture && spriteImageView == image_view) {
2728 spriteWidth = width;
2729 spriteHeight = height;
2730 spriteLoaded =
true;
2733 auto cached_descriptor = externalDescriptorSets.find(image_view);
2734 descriptorSet = (cached_descriptor != externalDescriptorSets.end()) ? cached_descriptor->second : VK_NULL_HANDLE;
2735 descriptorSetPool = VK_NULL_HANDLE;
2736 if (!externalTexture) {
2737 destroyTextureDescriptorPools();
2738 }
else if (extendedDescriptorPool != VK_NULL_HANDLE) {
2739 vkDeviceWaitIdle(device);
2740 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
2741 extendedDescriptorPool = VK_NULL_HANDLE;
2742 extendedDescriptorSet = VK_NULL_HANDLE;
2744 if (!externalTexture && spriteImageView != VK_NULL_HANDLE) {
2745 vkDestroyImageView(device, spriteImageView,
nullptr);
2747 if (!externalTexture && spriteImage != VK_NULL_HANDLE) {
2748 vkDestroyImage(device, spriteImage,
nullptr);
2750 if (!externalTexture && spriteImageMemory != VK_NULL_HANDLE) {
2751 vkFreeMemory(device, spriteImageMemory,
nullptr);
2753 spriteImageView = image_view;
2754 spriteImage = VK_NULL_HANDLE;
2755 spriteImageMemory = VK_NULL_HANDLE;
2756 externalTexture =
true;
2757 spriteWidth = width;
2758 spriteHeight = height;
2759 spriteLoaded =
true;
2763 if (!externalTexture && externalDescriptorSets.empty()) {
2766 destroyTextureDescriptorPools();
2771 recordCudaReadyBarrier(cmdBuffer);
2776 uint32_t screenWidth, uint32_t screenHeight) {
2777 if (drawQueue.empty() || !spriteLoaded || !quadBufferCreated) {
2780 if (descriptorSet == VK_NULL_HANDLE) {
2781 descriptorSet = createDescriptorSet(spriteImageView);
2782 if (externalTexture) {
2783 externalDescriptorSets[spriteImageView] = descriptorSet;
2787 if (instancingEnabled && instancedPipeline != VK_NULL_HANDLE && instanceBuffer != VK_NULL_HANDLE) {
2788 uint32_t instanceCount =
static_cast<uint32_t
>(drawQueue.size());
2790 if (instanceCount > instanceBufferCapacity) {
2791 ensureInstanceBuffer(instanceCount * 2);
2794 SpriteInstanceData *dst =
static_cast<SpriteInstanceData *
>(instanceBufferMapped);
2795 for (uint32_t i = 0; i < instanceCount; ++i) {
2796 const auto &cmd = drawQueue[i];
2797 dst[i].posX = cmd.x;
2798 dst[i].posY = cmd.y;
2799 dst[i].sizeW = cmd.w;
2800 dst[i].sizeH = cmd.h;
2801 dst[i].params[0] = cmd.params.x;
2802 dst[i].params[1] = cmd.params.y;
2803 dst[i].params[2] = cmd.params.z;
2804 dst[i].params[3] = cmd.params.w;
2807 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, instancedPipeline);
2808 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, instancedPipelineLayout,
2809 0, 1, &descriptorSet, 0,
nullptr);
2811 VkBuffer buffers[] = {quadVertexBuffer, instanceBuffer};
2812 VkDeviceSize bufOffsets[] = {0, 0};
2813 vkCmdBindVertexBuffers(cmdBuffer, 0, 2, buffers, bufOffsets);
2814 vkCmdBindIndexBuffer(cmdBuffer, quadIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
2816 float screenSize[2] = {
static_cast<float>(screenWidth),
static_cast<float>(screenHeight)};
2817 vkCmdPushConstants(cmdBuffer, instancedPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT,
2818 0,
sizeof(screenSize), screenSize);
2820 vkCmdDrawIndexed(cmdBuffer, 6, instanceCount, 0, 0, 0);
2824 VkPipelineLayout layoutToUse = (customPipeline != VK_NULL_HANDLE) ? customPipelineLayout : pipelineLayout;
2825 if (customPipeline != VK_NULL_HANDLE) {
2826 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, customPipeline);
2830 if (extendedUBOEnabled && customPipeline != VK_NULL_HANDLE) {
2831 updateExtendedUBO();
2832 if (extendedDescriptorSet == VK_NULL_HANDLE) {
2833 createExtendedDescriptorSet();
2835 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layoutToUse,
2836 0, 1, &extendedDescriptorSet, 0,
nullptr);
2838 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layoutToUse,
2839 0, 1, &descriptorSet, 0,
nullptr);
2842 VkBuffer vertexBuffers[] = {quadVertexBuffer};
2843 VkDeviceSize offsets[] = {0};
2844 vkCmdBindVertexBuffers(cmdBuffer, 0, 1, vertexBuffers, offsets);
2845 vkCmdBindIndexBuffer(cmdBuffer, quadIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
2847 for (
const auto &cmd : drawQueue) {
2848 struct SpritePushConstants {
2859 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}};
2861 vkCmdPushConstants(cmdBuffer, layoutToUse, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
2862 0,
sizeof(SpritePushConstants), &pc);
2864 vkCmdDrawIndexed(cmdBuffer, 6, 1, 0, 0, 0);
2872 void VK_Sprite::createDescriptorPool() {
2873 VkDescriptorPoolSize poolSize{};
2874 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
2875 poolSize.descriptorCount = nextDescriptorPoolSets;
2877 VkDescriptorPoolCreateInfo poolInfo{};
2878 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
2879 poolInfo.poolSizeCount = 1;
2880 poolInfo.pPoolSizes = &poolSize;
2881 poolInfo.maxSets = nextDescriptorPoolSets;
2882 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
2884 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo,
nullptr, &descriptorPool));
2885 descriptorPools.push_back(descriptorPool);
2886 if (nextDescriptorPoolSets <= (std::numeric_limits<uint32_t>::max() / 2U)) {
2887 nextDescriptorPoolSets *= 2U;
2891 void VK_Sprite::destroyDescriptorPools() {
2892 for (VkDescriptorPool pool : descriptorPools) {
2893 if (pool != VK_NULL_HANDLE) {
2894 vkDestroyDescriptorPool(device, pool,
nullptr);
2897 descriptorPools.clear();
2898 descriptorPool = VK_NULL_HANDLE;
2899 descriptorSetPool = VK_NULL_HANDLE;
2900 descriptorSet = VK_NULL_HANDLE;
2901 externalDescriptorSets.clear();
2902 nextDescriptorPoolSets = 16;
2905 void VK_Sprite::destroyTextureDescriptorPools() {
2906 if (!descriptorPools.empty() || extendedDescriptorPool != VK_NULL_HANDLE) {
2907 vkDeviceWaitIdle(device);
2909 destroyDescriptorPools();
2910 if (extendedDescriptorPool != VK_NULL_HANDLE) {
2911 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
2912 extendedDescriptorPool = VK_NULL_HANDLE;
2913 extendedDescriptorSet = VK_NULL_HANDLE;
2917 VkDescriptorSet VK_Sprite::createDescriptorSet(VkImageView imageView) {
2918 if (descriptorSetLayout == VK_NULL_HANDLE) {
2919 throw mxvk::Exception(
"VKSprite::createDescriptorSet called before setDescriptorSetLayout");
2922 if (descriptorPool == VK_NULL_HANDLE) {
2923 createDescriptorPool();
2926 VkDescriptorSetAllocateInfo allocInfo{};
2927 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
2928 allocInfo.descriptorPool = descriptorPool;
2929 allocInfo.descriptorSetCount = 1;
2930 allocInfo.pSetLayouts = &descriptorSetLayout;
2932 VkDescriptorSet descSet = VK_NULL_HANDLE;
2933 VkResult allocateResult = vkAllocateDescriptorSets(device, &allocInfo, &descSet);
2934 if (allocateResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocateResult == VK_ERROR_FRAGMENTED_POOL) {
2935 createDescriptorPool();
2936 allocInfo.descriptorPool = descriptorPool;
2937 allocateResult = vkAllocateDescriptorSets(device, &allocInfo, &descSet);
2939 if (allocateResult != VK_SUCCESS) {
2940 throw mxvk::Exception(std::format(
"Fatal : VkResult is \"{}\" in {} at line {}",
static_cast<int>(allocateResult), __FILE__, __LINE__));
2942 descriptorSetPool = allocInfo.descriptorPool;
2944 VkDescriptorImageInfo imageInfo{};
2945 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2946 imageInfo.imageView = imageView;
2949 VkWriteDescriptorSet descriptorWrite{};
2950 descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
2951 descriptorWrite.dstSet = descSet;
2952 descriptorWrite.dstBinding = 0;
2953 descriptorWrite.dstArrayElement = 0;
2954 descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
2955 descriptorWrite.descriptorCount = 1;
2956 descriptorWrite.pImageInfo = &imageInfo;
2958 vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0,
nullptr);
2963 void VK_Sprite::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
2964 VkMemoryPropertyFlags properties, VkBuffer &buffer,
2965 VkDeviceMemory &bufferMemory) {
2966 VkBuffer newBuffer = VK_NULL_HANDLE;
2967 VkDeviceMemory newMemory = VK_NULL_HANDLE;
2969 VkBufferCreateInfo bufferInfo{};
2970 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
2971 bufferInfo.size = size;
2972 bufferInfo.usage = usage;
2973 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2976 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo,
nullptr, &newBuffer));
2978 VkMemoryRequirements memRequirements;
2979 vkGetBufferMemoryRequirements(device, newBuffer, &memRequirements);
2981 VkMemoryAllocateInfo allocInfo{};
2982 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
2983 allocInfo.allocationSize = memRequirements.size;
2984 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
2985 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
2988 if (newBuffer != VK_NULL_HANDLE) {
2989 vkDestroyBuffer(device, newBuffer,
nullptr);
2991 if (newMemory != VK_NULL_HANDLE) {
2992 vkFreeMemory(device, newMemory,
nullptr);
2997 if (buffer != VK_NULL_HANDLE) {
2998 vkDestroyBuffer(device, buffer,
nullptr);
3000 if (bufferMemory != VK_NULL_HANDLE) {
3001 vkFreeMemory(device, bufferMemory,
nullptr);
3004 bufferMemory = newMemory;
3007 uint32_t VK_Sprite::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
3008 VkPhysicalDeviceMemoryProperties memProperties;
3009 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
3011 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
3012 if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
3017 throw mxvk::Exception(
"Failed to find suitable memory type!");
3020 void VK_Sprite::transitionImageLayout(VkImage image, VkImageLayout oldLayout,
3021 VkImageLayout newLayout, uint32_t baseArrayLayer,
3022 uint32_t layerCount) {
3023 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
3025 VkImageMemoryBarrier barrier{};
3026 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
3027 barrier.oldLayout = oldLayout;
3028 barrier.newLayout = newLayout;
3029 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3030 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3031 barrier.image = image;
3032 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3033 barrier.subresourceRange.baseMipLevel = 0;
3034 barrier.subresourceRange.levelCount = 1;
3035 barrier.subresourceRange.baseArrayLayer = baseArrayLayer;
3036 barrier.subresourceRange.layerCount = layerCount;
3038 VkPipelineStageFlags sourceStage;
3039 VkPipelineStageFlags destinationStage;
3041 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
3042 barrier.srcAccessMask = 0;
3043 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
3044 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
3045 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
3046 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
3047 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
3048 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
3049 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
3050 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
3051 }
else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
3052 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
3053 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
3054 sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
3055 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
3056 }
else if (oldLayout == VK_IMAGE_LAYOUT_GENERAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
3057 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
3058 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
3059 sourceStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
3060 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
3062 throw std::invalid_argument(
"unsupported layout transition!");
3065 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
3066 endSingleTimeCommands(commandBuffer);
3069 void VK_Sprite::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width,
3070 uint32_t height, uint32_t baseArrayLayer,
3071 uint32_t layerCount) {
3072 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
3074 VkBufferImageCopy region{};
3075 region.bufferOffset = 0;
3076 region.bufferRowLength = 0;
3077 region.bufferImageHeight = 0;
3078 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3079 region.imageSubresource.mipLevel = 0;
3080 region.imageSubresource.baseArrayLayer = baseArrayLayer;
3081 region.imageSubresource.layerCount = layerCount;
3082 region.imageOffset = {0, 0, 0};
3083 region.imageExtent = {width, height, 1};
3085 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
3087 endSingleTimeCommands(commandBuffer);
3090 VkCommandBuffer VK_Sprite::beginSingleTimeCommands() {
3091 VkCommandBufferAllocateInfo allocInfo{};
3092 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
3093 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
3094 allocInfo.commandPool = commandPool;
3095 allocInfo.commandBufferCount = 1;
3097 VkCommandBuffer commandBuffer;
3098 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
3100 VkCommandBufferBeginInfo beginInfo{};
3101 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
3102 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
3106 return commandBuffer;
3109 void VK_Sprite::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
3112 VkSubmitInfo submitInfo{};
3113 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
3114 submitInfo.commandBufferCount = 1;
3115 submitInfo.pCommandBuffers = &commandBuffer;
3117 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE));
3120 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
3123 void VK_Sprite::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling,
3124 VkImageUsageFlags usage, VkMemoryPropertyFlags properties,
3125 VkImage &image, VkDeviceMemory &imageMemory,
3126 uint32_t arrayLayers, VkImageType imageType) {
3127 VkImage newImage = VK_NULL_HANDLE;
3128 VkDeviceMemory newMemory = VK_NULL_HANDLE;
3130 VkImageCreateInfo imageInfo{};
3131 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
3132 imageInfo.imageType = imageType;
3133 imageInfo.extent.width = width;
3134 imageInfo.extent.height = height;
3135 imageInfo.extent.depth = 1;
3136 imageInfo.mipLevels = 1;
3137 imageInfo.arrayLayers = arrayLayers;
3138 imageInfo.format = format;
3139 imageInfo.tiling = tiling;
3140 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
3141 imageInfo.usage = usage;
3142 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
3143 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
3146 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo,
nullptr, &newImage));
3148 VkMemoryRequirements memRequirements;
3149 vkGetImageMemoryRequirements(device, newImage, &memRequirements);
3151 VkMemoryAllocateInfo allocInfo{};
3152 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
3153 allocInfo.allocationSize = memRequirements.size;
3154 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
3155 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
3158 if (newImage != VK_NULL_HANDLE) {
3159 vkDestroyImage(device, newImage,
nullptr);
3161 if (newMemory != VK_NULL_HANDLE) {
3162 vkFreeMemory(device, newMemory,
nullptr);
3167 if (image != VK_NULL_HANDLE) {
3168 vkDestroyImage(device, image,
nullptr);
3170 if (imageMemory != VK_NULL_HANDLE) {
3171 vkFreeMemory(device, imageMemory,
nullptr);
3174 imageMemory = newMemory;
3177 VkImageView VK_Sprite::createImageView(VkImage image, VkFormat format,
3178 VkImageViewType viewType, uint32_t layerCount) {
3179 VkImageViewCreateInfo viewInfo{};
3180 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
3181 viewInfo.image = image;
3182 viewInfo.viewType = viewType;
3183 viewInfo.format = format;
3184 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3185 viewInfo.subresourceRange.baseMipLevel = 0;
3186 viewInfo.subresourceRange.levelCount = 1;
3187 viewInfo.subresourceRange.baseArrayLayer = 0;
3188 viewInfo.subresourceRange.layerCount = layerCount;
3190 VkImageView imageView;
3191 VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo,
nullptr, &imageView));
3195 SDL_Surface *VK_Sprite::convertToRGBA(SDL_Surface *surface) {
3197 SDL_Surface *converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
3201 std::vector<char> VK_Sprite::readShaderFile(
const std::string &filename) {
3202 std::vector<std::filesystem::path> candidates{};
3203 const std::filesystem::path requested(filename);
3205 if (requested.is_absolute() || requested.has_parent_path()) {
3206 candidates.push_back(requested);
3208 if (
const char *basePath = SDL_GetBasePath(); basePath !=
nullptr) {
3209 const std::filesystem::path executableDir(basePath);
3210 candidates.push_back(executableDir /
"data" / requested);
3211 candidates.push_back(executableDir / requested);
3213 candidates.push_back(std::filesystem::path(
"data") / requested);
3214 candidates.push_back(requested);
3218 for (
const std::filesystem::path &candidate : candidates) {
3219 file.open(candidate, std::ios::ate | std::ios::binary);
3220 if (file.is_open()) {
3226 if (!file.is_open()) {
3227 throw mxvk::Exception(
"Failed to open shader file: " + filename);
3230 size_t fileSize =
static_cast<size_t>(file.tellg());
3231 std::vector<char> buffer(fileSize);
3233 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).
static constexpr std::size_t MAX_CUSTOM_UNIFORMS
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 shareHistoryTexture(const VK_Sprite &source)
Bind another sprite's history array without taking ownership.
void updateSpectrumHistoryTexture(const float *magnitudes, uint32_t bins)
Upload one FFT spectrum into the next history layer.
void setCommandPool(VkCommandPool pool)
Rebind the command pool used for upload/staging operations.
void enableComputeShader(const std::string &path, uint32_t localSizeX, uint32_t localSizeY, uint32_t localSizeZ=1)
Build a compute image-effect pipeline for this sprite.
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 updateSpectrumTexture(const float *magnitudes, uint32_t bins)
Replace the current floating-point spectrum data.
void clearExternalTextureDescriptors()
void createEmptySprite(int width, int height, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a blank (un-initialised) sprite texture.
uint32_t enableSpectrumHistoryTexture(uint32_t bins, uint32_t layers)
Allocate a shader-readable FFT spectrum-history array.
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 enableHistoryTexture(uint32_t width, uint32_t height, uint32_t layers)
Allocate a shader-readable RGBA history texture array.
void setCustomUniforms(const std::vector< float > &values)
Upload ordered custom float values to the extended UBO.
void dispatchCompute(VkCommandBuffer cmdBuffer, VkImageView inputView, VkImageView outputView, uint32_t width, uint32_t height)
Dispatch the compute effect between two full-frame images.
void updateHistoryTexture(const void *pixels, int width, int height, int pitch=0)
Upload one RGBA frame into the next history layer.
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 enableSpectrumTexture(uint32_t bins)
Allocate a shader-readable 1-D floating-point spectrum texture.
void rebuildPipeline()
Destroy and recreate the custom graphics pipeline.
void setAudioBands(float low, float mid, float high, float reserved=0.0f)
Upload audio frequency-band energy to the extended UBO.
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.