20 const uint32_t bits = std::bit_cast<uint32_t>(value);
21 const uint32_t sign = (bits >> 16U) & 0x8000U;
22 int32_t exponent =
static_cast<int32_t
>((bits >> 23U) & 0xFFU) - 127;
23 uint32_t mantissa = bits & 0x7FFFFFU;
25 return static_cast<uint16_t
>(sign);
28 mantissa |= 0x800000U;
29 const uint32_t shift =
static_cast<uint32_t
>(-exponent - 14);
30 const uint32_t rounded = (mantissa + (1U << (shift + 12U))) >> (shift + 13U);
31 return static_cast<uint16_t
>(sign | rounded);
34 return static_cast<uint16_t
>(sign | 0x7C00U);
36 uint32_t half_exponent =
static_cast<uint32_t
>(exponent + 15);
38 if ((mantissa & 0x800000U) != 0U) {
42 if (half_exponent >= 31U) {
43 return static_cast<uint16_t
>(sign | 0x7C00U);
45 return static_cast<uint16_t
>(sign | (half_exponent << 10U) | (mantissa >> 13U));
49 VK_Sprite::VK_Sprite(VkDevice dev, VkPhysicalDevice physDev, VkQueue gQueue, VkCommandPool cmdPool) : device(dev), physicalDevice(physDev), graphicsQueue(gQueue), commandPool(cmdPool) { std::cout <<
"mxvk: Created Sprite\n"; }
54 if (pool == commandPool) {
60 destroyStagingResources();
65 if (filter != VK_FILTER_NEAREST && filter != VK_FILTER_LINEAR) {
66 throw mxvk::Exception(
"VKSprite::setTextureFilter supports only nearest or linear filtering");
68 if (textureFilter == filter) {
72 textureFilter = filter;
74 destroyTextureDescriptorPools();
80 std::cout <<
"mxvk: Sprite texture filter set to " << (textureFilter == VK_FILTER_NEAREST ?
"nearest\n" :
"linear\n");
84 vkDeviceWaitIdle(device);
87 destroyStagingResources();
91 if (quadVertexBuffer != VK_NULL_HANDLE) {
92 std::cout <<
"vk: destroying sprite quad vertex buffer\n";
93 vkDestroyBuffer(device, quadVertexBuffer,
nullptr);
94 vkFreeMemory(device, quadVertexBufferMemory,
nullptr);
96 if (quadIndexBuffer != VK_NULL_HANDLE) {
97 std::cout <<
"vk: destroying sprite quad index buffer\n";
98 vkDestroyBuffer(device, quadIndexBuffer,
nullptr);
99 vkFreeMemory(device, quadIndexBufferMemory,
nullptr);
102 destroyTextureDescriptorPools();
105 std::cout <<
"vk: destroying sprite sampler\n";
109 if (!externalTexture && spriteImageView != VK_NULL_HANDLE) {
110 std::cout <<
"vk: destroying sprite image view\n";
111 vkDestroyImageView(device, spriteImageView,
nullptr);
114 if (!externalTexture && spriteImage != VK_NULL_HANDLE) {
115 std::cout <<
"vk: destroying sprite image\n";
116 vkDestroyImage(device, spriteImage,
nullptr);
117 std::cout <<
"vk: freeing sprite image memory\n";
118 vkFreeMemory(device, spriteImageMemory,
nullptr);
121 if (fragmentShaderModule != VK_NULL_HANDLE) {
122 vkDestroyShaderModule(device, fragmentShaderModule,
nullptr);
125 if (customPipeline != VK_NULL_HANDLE) {
126 std::cout <<
"vk: destroying sprite custom pipeline\n";
127 vkDestroyPipeline(device, customPipeline,
nullptr);
130 if (customPipelineLayout != VK_NULL_HANDLE) {
131 std::cout <<
"vk: destroying sprite custom pipeline layout\n";
132 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
135 destroyComputePipeline();
136 if (computeShaderModule != VK_NULL_HANDLE) {
137 vkDestroyShaderModule(device, computeShaderModule,
nullptr);
138 computeShaderModule = VK_NULL_HANDLE;
141 destroyExtendedUBO();
142 destroyHistoryTexture();
143 destroySpectrumTexture();
144 destroySpectrumHistoryTexture();
145 destroyInstanceResources();
148 void VK_Sprite::destroySpriteResources() {
149 destroyStagingResources();
151 destroyCudaInterop();
154 destroyTextureDescriptorPools();
157 std::cout <<
"vk: destroying sprite sampler\n";
162 if (!externalTexture && spriteImageView != VK_NULL_HANDLE) {
163 std::cout <<
"vk: destroying sprite image view\n";
164 vkDestroyImageView(device, spriteImageView,
nullptr);
165 spriteImageView = VK_NULL_HANDLE;
168 if (!externalTexture && spriteImage != VK_NULL_HANDLE) {
169 std::cout <<
"vk: destroying sprite image\n";
170 vkDestroyImage(device, spriteImage,
nullptr);
171 spriteImage = VK_NULL_HANDLE;
173 if (!externalTexture && spriteImageMemory != VK_NULL_HANDLE) {
174 std::cout <<
"vk: freeing sprite image memory\n";
175 vkFreeMemory(device, spriteImageMemory,
nullptr);
176 spriteImageMemory = VK_NULL_HANDLE;
178 externalTexture =
false;
180 if (fragmentShaderModule != VK_NULL_HANDLE) {
181 vkDestroyShaderModule(device, fragmentShaderModule,
nullptr);
182 fragmentShaderModule = VK_NULL_HANDLE;
185 if (customPipeline != VK_NULL_HANDLE) {
186 std::cout <<
"vk: destroying sprite custom pipeline\n";
187 vkDestroyPipeline(device, customPipeline,
nullptr);
188 customPipeline = VK_NULL_HANDLE;
191 if (customPipelineLayout != VK_NULL_HANDLE) {
192 std::cout <<
"vk: destroying sprite custom pipeline layout\n";
193 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
194 customPipelineLayout = VK_NULL_HANDLE;
197 hasCustomShader =
false;
198 spriteLoaded =
false;
202 if (extendedUBOEnabled)
204 extendedUBOEnabled =
true;
206 createExtendedDescriptorSetLayout();
220 void VK_Sprite::setAudioBands(
float low,
float mid,
float high,
float reserved) { extendedUBOData.audio_bands = glm::vec4(low, mid, high, reserved); }
227 extendedUBOData.custom_uniforms.fill(glm::vec4(0.0f));
228 for (std::size_t index = 0; index < values.size(); ++index) {
229 extendedUBOData.custom_uniforms[index / 4][index % 4] = values[index];
233 void VK_Sprite::enableHistoryTexture(uint32_t width, uint32_t height, uint32_t layers) { enableHistoryTextureWithFormat(width, height, layers, VK_FORMAT_R8G8B8A8_UNORM); }
237 void VK_Sprite::enableHistoryTextureWithFormat(uint32_t width, uint32_t height, uint32_t layers, VkFormat format) {
238 if (width == 0 || height == 0 || layers == 0) {
239 throw mxvk::Exception(
"VKSprite::enableHistoryTexture requires positive dimensions and layer count");
242 if (historyTextureEnabled && historyWidth == width && historyHeight == height && historyLayers == layers && historyImageFormat == format) {
246 if (!extendedUBOEnabled) {
250 vkDeviceWaitIdle(device);
251 destroyHistoryTexture();
253 historyWidth = width;
254 historyHeight = height;
255 historyLayers = layers;
257 historyImageFormat = format;
259 if (format == VK_FORMAT_R8G8B8A8_UNORM) {
261 createCudaExportableImage(width, height, layers, historyImage, historyImageMemory, cudaHistoryExportMemorySize);
262 cudaHistoryInteropUnavailableLogged =
false;
263 }
catch (
const std::exception &exception) {
264 std::cout << std::format(
"mxvk: CUDA exportable history image unavailable: {}; using "
265 "CPU staging uploads\n",
267 createImage(width, height, format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, historyImage, historyImageMemory, layers);
270 createImage(width, height, format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, historyImage, historyImageMemory, layers);
273 createImage(width, height, format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, historyImage, historyImageMemory, layers);
276 const VkDeviceSize bytes_per_pixel = format == VK_FORMAT_R16G16B16A16_SFLOAT ? 8U : 4U;
277 const VkDeviceSize layerSize =
static_cast<VkDeviceSize
>(width) * height * bytes_per_pixel;
278 const VkDeviceSize imageSize = layerSize * layers;
279 VkBuffer stagingBuffer = VK_NULL_HANDLE;
280 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
281 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
283 void *data =
nullptr;
284 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
285 memset(data, 0,
static_cast<std::size_t
>(imageSize));
286 vkUnmapMemory(device, stagingMemory);
288 transitionImageLayout(historyImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, layers);
290 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
291 std::vector<VkBufferImageCopy> regions(layers);
292 for (uint32_t layer = 0; layer < layers; ++layer) {
293 VkBufferImageCopy ®ion = regions[layer];
294 region.bufferOffset = layerSize * layer;
295 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
296 region.imageSubresource.mipLevel = 0;
297 region.imageSubresource.baseArrayLayer = layer;
298 region.imageSubresource.layerCount = 1;
299 region.imageExtent = {width, height, 1};
301 vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, historyImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
static_cast<uint32_t
>(regions.size()), regions.data());
302 endSingleTimeCommands(commandBuffer);
304 transitionImageLayout(historyImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, layers);
305 vkDestroyBuffer(device, stagingBuffer,
nullptr);
306 vkFreeMemory(device, stagingMemory,
nullptr);
308 historyImageView = createImageView(historyImage, format, VK_IMAGE_VIEW_TYPE_2D_ARRAY, layers);
309 historyTextureEnabled =
true;
310 historyTextureShared =
false;
311 recreateExtendedDescriptorLayout();
315 if (source.device != device) {
316 throw mxvk::Exception(
"VKSprite::shareHistoryTexture requires sprites on the same Vulkan device");
318 if (!source.historyTextureEnabled || source.historyImageView == VK_NULL_HANDLE || source.historyLayers == 0) {
319 throw mxvk::Exception(
"VKSprite::shareHistoryTexture source has no enabled history texture");
321 if (&source ==
this) {
322 throw mxvk::Exception(
"VKSprite::shareHistoryTexture cannot share a sprite with itself");
325 if (!extendedUBOEnabled) {
329 vkDeviceWaitIdle(device);
330 destroyHistoryTexture();
331 historyImageView = source.historyImageView;
332 historyImageFormat = source.historyImageFormat;
333 historyWidth = source.historyWidth;
334 historyHeight = source.historyHeight;
335 historyLayers = source.historyLayers;
336 historyHead = source.historyHead;
337 historyTextureEnabled =
true;
338 historyTextureShared =
true;
339 recreateExtendedDescriptorLayout();
343 if (historyImageFormat == VK_FORMAT_R16G16B16A16_SFLOAT) {
344 const int source_pitch = pitch > 0 ? pitch : width * 4;
345 if (pixels ==
nullptr || width <= 0 || height <= 0 || source_pitch < width * 4) {
346 throw mxvk::Exception(
"VKSprite::updateHistoryTexture received invalid RGBA8 pixels");
348 std::vector<uint16_t> converted(
static_cast<std::size_t
>(width) * height * 4U);
349 const auto *source =
static_cast<const uint8_t *
>(pixels);
350 for (
int row = 0; row < height; ++row) {
351 const uint8_t *source_row = source +
static_cast<std::size_t
>(row) * source_pitch;
352 uint16_t *destination_row = converted.data() +
static_cast<std::size_t
>(row) * width * 4U;
353 for (
int component = 0; component < width * 4; ++component) {
354 destination_row[component] = float_to_half(source_row[component] / 255.0F);
357 uploadHistoryTextureBytes(converted.data(), width, height, width * 8, 8);
360 uploadHistoryTextureBytes(pixels, width, height, pitch, 4);
364 if (historyImageFormat != VK_FORMAT_R16G16B16A16_SFLOAT) {
365 throw mxvk::Exception(
"VKSprite::updateHistoryTextureRgba16 requires RGBA16F history");
367 const int source_pitch = pitch > 0 ? pitch : width * 8;
368 if (pixels ==
nullptr || width <= 0 || height <= 0 || source_pitch < width * 8) {
369 throw mxvk::Exception(
"VKSprite::updateHistoryTextureRgba16 received invalid pixels");
371 std::vector<uint16_t> converted(
static_cast<std::size_t
>(width) * height * 4U);
372 const auto *source =
reinterpret_cast<const uint8_t *
>(pixels);
373 for (
int row = 0; row < height; ++row) {
374 const auto *source_row =
reinterpret_cast<const uint16_t *
>(source +
static_cast<std::size_t
>(row) * source_pitch);
375 uint16_t *destination_row = converted.data() +
static_cast<std::size_t
>(row) * width * 4U;
376 for (
int component = 0; component < width * 4; ++component) {
377 destination_row[component] = float_to_half(source_row[component] / 65535.0F);
380 uploadHistoryTextureBytes(converted.data(), width, height, width * 8, 8);
383 void VK_Sprite::uploadHistoryTextureBytes(
const void *pixels,
int width,
int height,
int pitch,
int bytesPerPixel) {
384 if (!historyTextureEnabled || historyImage == VK_NULL_HANDLE) {
385 throw mxvk::Exception(
"VKSprite::updateHistoryTexture called before enableHistoryTexture");
387 if (pixels ==
nullptr) {
388 throw mxvk::Exception(
"VKSprite::updateHistoryTexture called with null pixel data");
390 if (width <= 0 || height <= 0 ||
static_cast<uint32_t
>(width) != historyWidth ||
static_cast<uint32_t
>(height) != historyHeight) {
391 throw mxvk::Exception(
"VKSprite::updateHistoryTexture dimensions do not match the history texture");
394 const int row_size = width * bytesPerPixel;
395 const int sourcePitch = pitch > 0 ? pitch : row_size;
396 if (sourcePitch < row_size) {
397 throw mxvk::Exception(
"VKSprite::updateHistoryTexture pitch is smaller than one RGBA row");
400 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) * height * bytesPerPixel;
401 createStagingResources(imageSize);
402 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
405 if (sourcePitch == row_size) {
406 memcpy(persistentStagingMapped, pixels,
static_cast<std::size_t
>(imageSize));
408 const auto *source =
static_cast<const uint8_t *
>(pixels);
409 auto *destination =
static_cast<uint8_t *
>(persistentStagingMapped);
410 for (
int row = 0; row < height; ++row) {
411 memcpy(destination +
static_cast<std::size_t
>(row * row_size), source +
static_cast<std::size_t
>(row * sourcePitch),
static_cast<std::size_t
>(row_size));
416 VkCommandBufferBeginInfo beginInfo{};
417 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
418 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
421 VkImageMemoryBarrier barrier{};
422 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
423 barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
424 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
425 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
426 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
427 barrier.image = historyImage;
428 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
429 barrier.subresourceRange.baseMipLevel = 0;
430 barrier.subresourceRange.levelCount = 1;
431 barrier.subresourceRange.baseArrayLayer = historyHead;
432 barrier.subresourceRange.layerCount = 1;
433 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
434 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
435 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
437 VkBufferImageCopy region{};
438 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
439 region.imageSubresource.mipLevel = 0;
440 region.imageSubresource.baseArrayLayer = historyHead;
441 region.imageSubresource.layerCount = 1;
442 region.imageExtent = {historyWidth, historyHeight, 1};
443 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer, historyImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
445 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
446 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
447 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
448 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
449 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
452 VkSubmitInfo submitInfo{};
453 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
454 submitInfo.commandBufferCount = 1;
455 submitInfo.pCommandBuffers = &uploadCmdBuffer;
456 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
457 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
459 historyHead = (historyHead + 1) % historyLayers;
464 throw mxvk::Exception(
"VKSprite::enableSpectrumTexture requires a positive bin count");
466 if (spectrumTextureEnabled && spectrumBins == bins) {
469 if (!extendedUBOEnabled) {
473 vkDeviceWaitIdle(device);
474 destroySpectrumTexture();
477 createImage(bins, 1, VK_FORMAT_R32_SFLOAT, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spectrumImage, spectrumImageMemory, 1, VK_IMAGE_TYPE_1D);
479 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(bins) *
sizeof(
float);
480 VkBuffer stagingBuffer = VK_NULL_HANDLE;
481 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
482 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
484 void *data =
nullptr;
485 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
486 memset(data, 0,
static_cast<std::size_t
>(imageSize));
487 vkUnmapMemory(device, stagingMemory);
489 transitionImageLayout(spectrumImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
490 copyBufferToImage(stagingBuffer, spectrumImage, bins, 1);
491 transitionImageLayout(spectrumImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
493 vkDestroyBuffer(device, stagingBuffer,
nullptr);
494 vkFreeMemory(device, stagingMemory,
nullptr);
496 spectrumImageView = createImageView(spectrumImage, VK_FORMAT_R32_SFLOAT, VK_IMAGE_VIEW_TYPE_1D);
497 spectrumTextureEnabled =
true;
498 recreateExtendedDescriptorLayout();
502 if (!spectrumTextureEnabled || spectrumImage == VK_NULL_HANDLE) {
503 throw mxvk::Exception(
"VKSprite::updateSpectrumTexture called before enableSpectrumTexture");
505 if (magnitudes ==
nullptr) {
506 throw mxvk::Exception(
"VKSprite::updateSpectrumTexture called with null data");
508 if (bins != spectrumBins) {
509 throw mxvk::Exception(
"VKSprite::updateSpectrumTexture bin count does not match the spectrum texture");
512 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(bins) *
sizeof(
float);
513 createStagingResources(imageSize);
514 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
516 memcpy(persistentStagingMapped, magnitudes,
static_cast<std::size_t
>(imageSize));
519 VkCommandBufferBeginInfo beginInfo{};
520 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
521 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
524 VkImageMemoryBarrier barrier{};
525 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
526 barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
527 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
528 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
529 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
530 barrier.image = spectrumImage;
531 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
532 barrier.subresourceRange.baseMipLevel = 0;
533 barrier.subresourceRange.levelCount = 1;
534 barrier.subresourceRange.baseArrayLayer = 0;
535 barrier.subresourceRange.layerCount = 1;
536 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
537 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
538 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
540 VkBufferImageCopy region{};
541 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
542 region.imageSubresource.mipLevel = 0;
543 region.imageSubresource.baseArrayLayer = 0;
544 region.imageSubresource.layerCount = 1;
545 region.imageExtent = {bins, 1, 1};
546 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer, spectrumImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
548 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
549 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
550 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
551 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
552 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
555 VkSubmitInfo submitInfo{};
556 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
557 submitInfo.commandBufferCount = 1;
558 submitInfo.pCommandBuffers = &uploadCmdBuffer;
559 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
560 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
564 if (bins == 0 || layers == 0) {
565 throw mxvk::Exception(
"VKSprite::enableSpectrumHistoryTexture requires positive bin and layer counts");
567 VkPhysicalDeviceProperties properties{};
568 vkGetPhysicalDeviceProperties(physicalDevice, &properties);
569 const uint32_t allocatedLayers = std::min(layers, properties.limits.maxImageArrayLayers);
570 if (allocatedLayers == 0) {
571 throw mxvk::Exception(
"VKSprite::enableSpectrumHistoryTexture is unavailable on this device");
573 if (spectrumHistoryTextureEnabled && spectrumHistoryBins == bins && spectrumHistoryLayers == allocatedLayers) {
574 return spectrumHistoryLayers;
576 if (!extendedUBOEnabled) {
580 vkDeviceWaitIdle(device);
581 destroySpectrumHistoryTexture();
583 if (allocatedLayers != layers) {
584 std::cerr <<
"vk: spectrum history clamped to device array-layer limit " << allocatedLayers <<
" (was " << layers <<
")\n";
586 spectrumHistoryBins = bins;
587 spectrumHistoryLayers = allocatedLayers;
588 spectrumHistoryHead = 0;
589 spectrumHistoryWriteIndex = 0;
590 extendedUBOData.audio_history = glm::vec4(0.0f,
static_cast<float>(allocatedLayers),
static_cast<float>(bins), 0.0f);
592 createImage(bins, 1, VK_FORMAT_R32_SFLOAT, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spectrumHistoryImage, spectrumHistoryImageMemory, allocatedLayers, VK_IMAGE_TYPE_1D);
594 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(bins) *
static_cast<VkDeviceSize
>(allocatedLayers) *
sizeof(float);
595 VkBuffer stagingBuffer = VK_NULL_HANDLE;
596 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
597 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
599 void *data =
nullptr;
600 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
601 memset(data, 0,
static_cast<std::size_t
>(imageSize));
602 vkUnmapMemory(device, stagingMemory);
604 transitionImageLayout(spectrumHistoryImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, allocatedLayers);
605 copyBufferToImage(stagingBuffer, spectrumHistoryImage, bins, 1, 0, allocatedLayers);
606 transitionImageLayout(spectrumHistoryImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, allocatedLayers);
608 vkDestroyBuffer(device, stagingBuffer,
nullptr);
609 vkFreeMemory(device, stagingMemory,
nullptr);
611 spectrumHistoryImageView = createImageView(spectrumHistoryImage, VK_FORMAT_R32_SFLOAT, VK_IMAGE_VIEW_TYPE_1D_ARRAY, allocatedLayers);
612 spectrumHistoryTextureEnabled =
true;
613 recreateExtendedDescriptorLayout();
614 return allocatedLayers;
618 if (!spectrumHistoryTextureEnabled || spectrumHistoryImage == VK_NULL_HANDLE) {
619 throw mxvk::Exception(
"VKSprite::updateSpectrumHistoryTexture called before enableSpectrumHistoryTexture");
621 if (magnitudes ==
nullptr) {
622 throw mxvk::Exception(
"VKSprite::updateSpectrumHistoryTexture called with null data");
624 if (bins != spectrumHistoryBins) {
625 throw mxvk::Exception(
"VKSprite::updateSpectrumHistoryTexture bin count does not match the history texture");
628 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(bins) *
sizeof(
float);
629 createStagingResources(imageSize);
630 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
632 memcpy(persistentStagingMapped, magnitudes,
static_cast<std::size_t
>(imageSize));
635 VkCommandBufferBeginInfo beginInfo{};
636 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
637 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
640 VkImageMemoryBarrier barrier{};
641 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
642 barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
643 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
644 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
645 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
646 barrier.image = spectrumHistoryImage;
647 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
648 barrier.subresourceRange.baseMipLevel = 0;
649 barrier.subresourceRange.levelCount = 1;
650 barrier.subresourceRange.baseArrayLayer = spectrumHistoryWriteIndex;
651 barrier.subresourceRange.layerCount = 1;
652 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
653 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
654 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
656 VkBufferImageCopy region{};
657 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
658 region.imageSubresource.mipLevel = 0;
659 region.imageSubresource.baseArrayLayer = spectrumHistoryWriteIndex;
660 region.imageSubresource.layerCount = 1;
661 region.imageExtent = {bins, 1, 1};
662 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer, spectrumHistoryImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
664 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
665 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
666 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
667 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
668 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
671 VkSubmitInfo submitInfo{};
672 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
673 submitInfo.commandBufferCount = 1;
674 submitInfo.pCommandBuffers = &uploadCmdBuffer;
675 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
676 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
678 spectrumHistoryHead = spectrumHistoryWriteIndex;
679 spectrumHistoryWriteIndex = (spectrumHistoryWriteIndex + 1) % spectrumHistoryLayers;
680 extendedUBOData.audio_history = glm::vec4(
static_cast<float>(spectrumHistoryHead),
static_cast<float>(spectrumHistoryLayers),
static_cast<float>(spectrumHistoryBins), 0.0f);
683 void VK_Sprite::createExtendedUBO() {
684 if (extendedUBOBuffer != VK_NULL_HANDLE)
686 createBuffer(
sizeof(SpriteExtendedUBO), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, extendedUBOBuffer, extendedUBOMemory);
687 VK_CHECK_RESULT(vkMapMemory(device, extendedUBOMemory, 0,
sizeof(SpriteExtendedUBO), 0, &extendedUBOMapped));
688 memset(extendedUBOMapped, 0,
sizeof(SpriteExtendedUBO));
691 void VK_Sprite::updateExtendedUBO() {
692 if (!extendedUBOEnabled || !extendedUBOMapped)
694 memcpy(extendedUBOMapped, &extendedUBOData,
sizeof(SpriteExtendedUBO));
697 void VK_Sprite::createExtendedDescriptorSetLayout() {
698 if (extendedDescriptorSetLayout != VK_NULL_HANDLE)
701 std::vector<VkDescriptorSetLayoutBinding> bindings(2);
703 bindings[0].binding = 0;
704 bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
705 bindings[0].descriptorCount = 1;
706 bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
707 bindings[0].pImmutableSamplers =
nullptr;
709 bindings[1].binding = 1;
710 bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
711 bindings[1].descriptorCount = 1;
712 bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
713 bindings[1].pImmutableSamplers =
nullptr;
714 if (historyTextureEnabled) {
715 VkDescriptorSetLayoutBinding historyBinding{};
716 historyBinding.binding = 2;
717 historyBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
718 historyBinding.descriptorCount = 1;
719 historyBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
720 bindings.push_back(historyBinding);
722 if (spectrumTextureEnabled) {
723 VkDescriptorSetLayoutBinding spectrumBinding{};
724 spectrumBinding.binding = 3;
725 spectrumBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
726 spectrumBinding.descriptorCount = 1;
727 spectrumBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
728 bindings.push_back(spectrumBinding);
730 if (spectrumHistoryTextureEnabled) {
731 VkDescriptorSetLayoutBinding spectrumHistoryBinding{};
732 spectrumHistoryBinding.binding = 4;
733 spectrumHistoryBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
734 spectrumHistoryBinding.descriptorCount = 1;
735 spectrumHistoryBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
736 bindings.push_back(spectrumHistoryBinding);
738 if (computeShaderModule != VK_NULL_HANDLE) {
739 VkDescriptorSetLayoutBinding outputBinding{};
740 outputBinding.binding = 5;
741 outputBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
742 outputBinding.descriptorCount = 1;
743 outputBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
744 bindings.push_back(outputBinding);
747 VkDescriptorSetLayoutCreateInfo layoutInfo{};
748 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
749 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
750 layoutInfo.pBindings = bindings.data();
752 VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &layoutInfo,
nullptr, &extendedDescriptorSetLayout));
753 ownExtendedDescriptorSetLayout =
true;
756 void VK_Sprite::createExtendedDescriptorSet() {
757 if (extendedDescriptorSetLayout == VK_NULL_HANDLE || spriteImageView == VK_NULL_HANDLE ||
spriteSampler == VK_NULL_HANDLE || extendedUBOBuffer == VK_NULL_HANDLE || (historyTextureEnabled && historyImageView == VK_NULL_HANDLE) || (spectrumTextureEnabled && spectrumImageView == VK_NULL_HANDLE) || (spectrumHistoryTextureEnabled && spectrumHistoryImageView == VK_NULL_HANDLE) || (computeShaderModule != VK_NULL_HANDLE && computeOutputImageView == VK_NULL_HANDLE))
760 if (extendedDescriptorPool != VK_NULL_HANDLE) {
761 vkDeviceWaitIdle(device);
762 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
763 extendedDescriptorPool = VK_NULL_HANDLE;
764 extendedDescriptorSet = VK_NULL_HANDLE;
767 std::array<VkDescriptorPoolSize, 3> poolSizes{};
768 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
769 poolSizes[0].descriptorCount = 1U +
static_cast<uint32_t
>(historyTextureEnabled) +
static_cast<uint32_t
>(spectrumTextureEnabled) +
static_cast<uint32_t
>(spectrumHistoryTextureEnabled);
770 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
771 poolSizes[1].descriptorCount = 1;
772 poolSizes[2].type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
773 poolSizes[2].descriptorCount = computeShaderModule != VK_NULL_HANDLE ? 1U : 0U;
775 VkDescriptorPoolCreateInfo poolInfo{};
776 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
777 poolInfo.poolSizeCount = computeShaderModule != VK_NULL_HANDLE ?
static_cast<uint32_t
>(poolSizes.size()) : 2U;
778 poolInfo.pPoolSizes = poolSizes.data();
779 poolInfo.maxSets = 1;
781 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo,
nullptr, &extendedDescriptorPool));
783 VkDescriptorSetAllocateInfo allocInfo{};
784 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
785 allocInfo.descriptorPool = extendedDescriptorPool;
786 allocInfo.descriptorSetCount = 1;
787 allocInfo.pSetLayouts = &extendedDescriptorSetLayout;
789 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &extendedDescriptorSet));
791 VkDescriptorImageInfo imageInfo{};
792 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
793 imageInfo.imageView = spriteImageView;
796 VkDescriptorBufferInfo bufferInfo{};
797 bufferInfo.buffer = extendedUBOBuffer;
798 bufferInfo.offset = 0;
799 bufferInfo.range =
sizeof(SpriteExtendedUBO);
801 VkDescriptorImageInfo historyImageInfo{};
802 historyImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
803 historyImageInfo.imageView = historyImageView;
806 VkDescriptorImageInfo spectrumImageInfo{};
807 spectrumImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
808 spectrumImageInfo.imageView = spectrumImageView;
811 VkDescriptorImageInfo spectrumHistoryImageInfo{};
812 spectrumHistoryImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
813 spectrumHistoryImageInfo.imageView = spectrumHistoryImageView;
816 VkDescriptorImageInfo outputImageInfo{};
817 outputImageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
818 outputImageInfo.imageView = computeOutputImageView;
820 std::vector<VkWriteDescriptorSet> writes(2);
821 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
822 writes[0].dstSet = extendedDescriptorSet;
823 writes[0].dstBinding = 0;
824 writes[0].dstArrayElement = 0;
825 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
826 writes[0].descriptorCount = 1;
827 writes[0].pImageInfo = &imageInfo;
829 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
830 writes[1].dstSet = extendedDescriptorSet;
831 writes[1].dstBinding = 1;
832 writes[1].dstArrayElement = 0;
833 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
834 writes[1].descriptorCount = 1;
835 writes[1].pBufferInfo = &bufferInfo;
837 if (historyTextureEnabled) {
838 VkWriteDescriptorSet historyWrite{};
839 historyWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
840 historyWrite.dstSet = extendedDescriptorSet;
841 historyWrite.dstBinding = 2;
842 historyWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
843 historyWrite.descriptorCount = 1;
844 historyWrite.pImageInfo = &historyImageInfo;
845 writes.push_back(historyWrite);
847 if (spectrumTextureEnabled) {
848 VkWriteDescriptorSet spectrumWrite{};
849 spectrumWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
850 spectrumWrite.dstSet = extendedDescriptorSet;
851 spectrumWrite.dstBinding = 3;
852 spectrumWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
853 spectrumWrite.descriptorCount = 1;
854 spectrumWrite.pImageInfo = &spectrumImageInfo;
855 writes.push_back(spectrumWrite);
857 if (spectrumHistoryTextureEnabled) {
858 VkWriteDescriptorSet spectrumHistoryWrite{};
859 spectrumHistoryWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
860 spectrumHistoryWrite.dstSet = extendedDescriptorSet;
861 spectrumHistoryWrite.dstBinding = 4;
862 spectrumHistoryWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
863 spectrumHistoryWrite.descriptorCount = 1;
864 spectrumHistoryWrite.pImageInfo = &spectrumHistoryImageInfo;
865 writes.push_back(spectrumHistoryWrite);
867 if (computeShaderModule != VK_NULL_HANDLE) {
868 VkWriteDescriptorSet outputWrite{};
869 outputWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
870 outputWrite.dstSet = extendedDescriptorSet;
871 outputWrite.dstBinding = 5;
872 outputWrite.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
873 outputWrite.descriptorCount = 1;
874 outputWrite.pImageInfo = &outputImageInfo;
875 writes.push_back(outputWrite);
878 vkUpdateDescriptorSets(device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
881 void VK_Sprite::recreateExtendedDescriptorLayout() {
882 vkDeviceWaitIdle(device);
884 if (customPipeline != VK_NULL_HANDLE) {
885 vkDestroyPipeline(device, customPipeline,
nullptr);
886 customPipeline = VK_NULL_HANDLE;
888 if (customPipelineLayout != VK_NULL_HANDLE) {
889 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
890 customPipelineLayout = VK_NULL_HANDLE;
892 destroyComputePipeline();
893 if (extendedDescriptorPool != VK_NULL_HANDLE) {
894 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
895 extendedDescriptorPool = VK_NULL_HANDLE;
896 extendedDescriptorSet = VK_NULL_HANDLE;
898 if (ownExtendedDescriptorSetLayout && extendedDescriptorSetLayout != VK_NULL_HANDLE) {
899 vkDestroyDescriptorSetLayout(device, extendedDescriptorSetLayout,
nullptr);
900 extendedDescriptorSetLayout = VK_NULL_HANDLE;
901 ownExtendedDescriptorSetLayout =
false;
904 createExtendedDescriptorSetLayout();
906 createComputePipeline();
909 void VK_Sprite::destroyHistoryTexture() {
911 if (!historyTextureShared) {
912 destroyCudaHistoryInterop();
915 if (!historyTextureShared && historyImageView != VK_NULL_HANDLE) {
916 vkDestroyImageView(device, historyImageView,
nullptr);
918 historyImageView = VK_NULL_HANDLE;
919 if (historyImage != VK_NULL_HANDLE) {
920 vkDestroyImage(device, historyImage,
nullptr);
921 historyImage = VK_NULL_HANDLE;
923 if (historyImageMemory != VK_NULL_HANDLE) {
924 vkFreeMemory(device, historyImageMemory,
nullptr);
925 historyImageMemory = VK_NULL_HANDLE;
927 historyTextureEnabled =
false;
928 historyTextureShared =
false;
933 historyImageFormat = VK_FORMAT_R8G8B8A8_UNORM;
936 void VK_Sprite::destroySpectrumTexture() {
937 if (spectrumImageView != VK_NULL_HANDLE) {
938 vkDestroyImageView(device, spectrumImageView,
nullptr);
939 spectrumImageView = VK_NULL_HANDLE;
941 if (spectrumImage != VK_NULL_HANDLE) {
942 vkDestroyImage(device, spectrumImage,
nullptr);
943 spectrumImage = VK_NULL_HANDLE;
945 if (spectrumImageMemory != VK_NULL_HANDLE) {
946 vkFreeMemory(device, spectrumImageMemory,
nullptr);
947 spectrumImageMemory = VK_NULL_HANDLE;
949 spectrumTextureEnabled =
false;
953 void VK_Sprite::destroySpectrumHistoryTexture() {
954 if (spectrumHistoryImageView != VK_NULL_HANDLE) {
955 vkDestroyImageView(device, spectrumHistoryImageView,
nullptr);
956 spectrumHistoryImageView = VK_NULL_HANDLE;
958 if (spectrumHistoryImage != VK_NULL_HANDLE) {
959 vkDestroyImage(device, spectrumHistoryImage,
nullptr);
960 spectrumHistoryImage = VK_NULL_HANDLE;
962 if (spectrumHistoryImageMemory != VK_NULL_HANDLE) {
963 vkFreeMemory(device, spectrumHistoryImageMemory,
nullptr);
964 spectrumHistoryImageMemory = VK_NULL_HANDLE;
966 spectrumHistoryTextureEnabled =
false;
967 spectrumHistoryBins = 0;
968 spectrumHistoryLayers = 0;
969 spectrumHistoryHead = 0;
970 spectrumHistoryWriteIndex = 0;
971 extendedUBOData.audio_history = glm::vec4(0.0f);
974 void VK_Sprite::destroyExtendedUBO() {
975 if (extendedDescriptorPool != VK_NULL_HANDLE) {
976 vkDeviceWaitIdle(device);
977 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
978 extendedDescriptorPool = VK_NULL_HANDLE;
979 extendedDescriptorSet = VK_NULL_HANDLE;
981 if (ownExtendedDescriptorSetLayout && extendedDescriptorSetLayout != VK_NULL_HANDLE) {
982 vkDestroyDescriptorSetLayout(device, extendedDescriptorSetLayout,
nullptr);
983 extendedDescriptorSetLayout = VK_NULL_HANDLE;
984 ownExtendedDescriptorSetLayout =
false;
986 if (extendedUBOBuffer != VK_NULL_HANDLE) {
987 if (extendedUBOMapped) {
988 vkUnmapMemory(device, extendedUBOMemory);
989 extendedUBOMapped =
nullptr;
991 vkDestroyBuffer(device, extendedUBOBuffer,
nullptr);
992 vkFreeMemory(device, extendedUBOMemory,
nullptr);
993 extendedUBOBuffer = VK_NULL_HANDLE;
994 extendedUBOMemory = VK_NULL_HANDLE;
996 extendedUBOEnabled =
false;
999 VkDeviceSize VK_Sprite::stagingAllocationSize(VkDeviceSize requiredSize)
const {
1000 VkDeviceSize allocationSize = 1;
1001 while (allocationSize < requiredSize && allocationSize <= (std::numeric_limits<VkDeviceSize>::max() / 2)) {
1002 allocationSize *= 2;
1004 return std::max(allocationSize, requiredSize);
1007 void VK_Sprite::createStagingResources(VkDeviceSize size) {
1008 const VkDeviceSize allocationSize = stagingAllocationSize(size);
1009 if (stagingResourcesCreated && persistentStagingSize >= size) {
1012 destroyStagingResources();
1014 createBuffer(allocationSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, persistentStagingBuffer, persistentStagingMemory);
1017 VK_CHECK_RESULT(vkMapMemory(device, persistentStagingMemory, 0, allocationSize, 0, &persistentStagingMapped));
1018 persistentStagingSize = allocationSize;
1019 VkCommandBufferAllocateInfo allocInfo{};
1020 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1021 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1022 allocInfo.commandPool = commandPool;
1023 allocInfo.commandBufferCount = 1;
1024 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &uploadCmdBuffer));
1025 VkFenceCreateInfo fenceInfo{};
1026 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1027 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
1028 VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo,
nullptr, &uploadFence));
1029 stagingResourcesCreated =
true;
1031 if (uploadCmdBuffer != VK_NULL_HANDLE) {
1032 vkFreeCommandBuffers(device, commandPool, 1, &uploadCmdBuffer);
1033 uploadCmdBuffer = VK_NULL_HANDLE;
1035 if (persistentStagingMapped) {
1036 vkUnmapMemory(device, persistentStagingMemory);
1037 persistentStagingMapped =
nullptr;
1039 if (persistentStagingBuffer != VK_NULL_HANDLE) {
1040 vkDestroyBuffer(device, persistentStagingBuffer,
nullptr);
1041 persistentStagingBuffer = VK_NULL_HANDLE;
1043 if (persistentStagingMemory != VK_NULL_HANDLE) {
1044 vkFreeMemory(device, persistentStagingMemory,
nullptr);
1045 persistentStagingMemory = VK_NULL_HANDLE;
1047 persistentStagingSize = 0;
1052 void VK_Sprite::destroyStagingResources() {
1053 if (!stagingResourcesCreated)
1056 if (uploadFence != VK_NULL_HANDLE) {
1057 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1058 vkDestroyFence(device, uploadFence,
nullptr);
1059 uploadFence = VK_NULL_HANDLE;
1061 if (uploadCmdBuffer != VK_NULL_HANDLE) {
1062 vkFreeCommandBuffers(device, commandPool, 1, &uploadCmdBuffer);
1063 uploadCmdBuffer = VK_NULL_HANDLE;
1065 if (persistentStagingBuffer != VK_NULL_HANDLE) {
1066 vkUnmapMemory(device, persistentStagingMemory);
1067 vkDestroyBuffer(device, persistentStagingBuffer,
nullptr);
1068 vkFreeMemory(device, persistentStagingMemory,
nullptr);
1069 persistentStagingBuffer = VK_NULL_HANDLE;
1070 persistentStagingMemory = VK_NULL_HANDLE;
1071 persistentStagingMapped =
nullptr;
1072 persistentStagingSize = 0;
1074 stagingResourcesCreated =
false;
1077 void VK_Sprite::destroyInstanceResources() {
1078 if (instanceBuffer != VK_NULL_HANDLE) {
1079 if (instanceBufferMapped) {
1080 vkUnmapMemory(device, instanceBufferMemory);
1081 instanceBufferMapped =
nullptr;
1083 vkDestroyBuffer(device, instanceBuffer,
nullptr);
1084 vkFreeMemory(device, instanceBufferMemory,
nullptr);
1085 instanceBuffer = VK_NULL_HANDLE;
1086 instanceBufferMemory = VK_NULL_HANDLE;
1087 instanceBufferCapacity = 0;
1089 if (instancedPipeline != VK_NULL_HANDLE) {
1090 vkDestroyPipeline(device, instancedPipeline,
nullptr);
1091 instancedPipeline = VK_NULL_HANDLE;
1093 if (instancedPipelineLayout != VK_NULL_HANDLE) {
1094 vkDestroyPipelineLayout(device, instancedPipelineLayout,
nullptr);
1095 instancedPipelineLayout = VK_NULL_HANDLE;
1097 instancingEnabled =
false;
1100 void VK_Sprite::ensureInstanceBuffer(uint32_t count) {
1101 if (instanceBufferCapacity >= count && instanceBuffer != VK_NULL_HANDLE)
1104 if (instanceBuffer != VK_NULL_HANDLE) {
1105 if (instanceBufferMapped) {
1106 vkUnmapMemory(device, instanceBufferMemory);
1107 instanceBufferMapped =
nullptr;
1109 vkDestroyBuffer(device, instanceBuffer,
nullptr);
1110 vkFreeMemory(device, instanceBufferMemory,
nullptr);
1111 instanceBuffer = VK_NULL_HANDLE;
1112 instanceBufferMemory = VK_NULL_HANDLE;
1115 VkDeviceSize size =
sizeof(SpriteInstanceData) * count;
1116 createBuffer(size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, instanceBuffer, instanceBufferMemory);
1118 VK_CHECK_RESULT(vkMapMemory(device, instanceBufferMemory, 0, size, 0, &instanceBufferMapped));
1119 instanceBufferCapacity = count;
1123 if (colorAttachmentFormat == VK_FORMAT_UNDEFINED || descriptorSetLayout == VK_NULL_HANDLE) {
1124 throw mxvk::Exception(
"VKSprite::enableInstancing called before color format/descriptorSetLayout set");
1126 ensureInstanceBuffer(maxInstances);
1128 instanceVertPath = instanceVertShaderPath;
1129 instanceFragPath = instanceFragShaderPath;
1130 createInstancedPipeline(instanceVertShaderPath, instanceFragShaderPath);
1131 instancingEnabled =
true;
1132 std::cout << std::format(
"mxvk: Instancing enabled (max {} instances)\n", maxInstances);
1135 void VK_Sprite::createInstancedPipeline(
const std::string &vertPath,
const std::string &fragPath) {
1136 if (instancedPipeline != VK_NULL_HANDLE) {
1137 vkDestroyPipeline(device, instancedPipeline,
nullptr);
1138 instancedPipeline = VK_NULL_HANDLE;
1140 if (instancedPipelineLayout != VK_NULL_HANDLE) {
1141 vkDestroyPipelineLayout(device, instancedPipelineLayout,
nullptr);
1142 instancedPipelineLayout = VK_NULL_HANDLE;
1145 auto vertShaderCode = readShaderFile(vertPath);
1146 auto fragShaderCode = readShaderFile(fragPath);
1150 VkPipelineShaderStageCreateInfo vertStageInfo{};
1151 vertStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1152 vertStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
1153 vertStageInfo.module = vertModule;
1154 vertStageInfo.pName =
"main";
1156 VkPipelineShaderStageCreateInfo fragStageInfo{};
1157 fragStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1158 fragStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1159 fragStageInfo.module = fragModule;
1160 fragStageInfo.pName =
"main";
1162 VkPipelineShaderStageCreateInfo shaderStages[] = {vertStageInfo, fragStageInfo};
1164 std::array<VkVertexInputBindingDescription, 2> bindingDescs{};
1165 bindingDescs[0].binding = 0;
1166 bindingDescs[0].stride =
sizeof(float) * 4;
1167 bindingDescs[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1168 bindingDescs[1].binding = 1;
1169 bindingDescs[1].stride =
sizeof(SpriteInstanceData);
1170 bindingDescs[1].inputRate = VK_VERTEX_INPUT_RATE_INSTANCE;
1172 std::array<VkVertexInputAttributeDescription, 4> attrDescs{};
1174 attrDescs[0].binding = 0;
1175 attrDescs[0].location = 0;
1176 attrDescs[0].format = VK_FORMAT_R32G32_SFLOAT;
1177 attrDescs[0].offset = 0;
1179 attrDescs[1].binding = 0;
1180 attrDescs[1].location = 1;
1181 attrDescs[1].format = VK_FORMAT_R32G32_SFLOAT;
1182 attrDescs[1].offset =
sizeof(float) * 2;
1184 attrDescs[2].binding = 1;
1185 attrDescs[2].location = 2;
1186 attrDescs[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
1187 attrDescs[2].offset = 0;
1189 attrDescs[3].binding = 1;
1190 attrDescs[3].location = 3;
1191 attrDescs[3].format = VK_FORMAT_R32G32B32A32_SFLOAT;
1192 attrDescs[3].offset =
sizeof(float) * 4;
1194 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
1195 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1196 vertexInputInfo.vertexBindingDescriptionCount =
static_cast<uint32_t
>(bindingDescs.size());
1197 vertexInputInfo.pVertexBindingDescriptions = bindingDescs.data();
1198 vertexInputInfo.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrDescs.size());
1199 vertexInputInfo.pVertexAttributeDescriptions = attrDescs.data();
1201 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1202 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1203 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1204 inputAssembly.primitiveRestartEnable = VK_FALSE;
1206 std::vector<VkDynamicState> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
1207 VkPipelineDynamicStateCreateInfo dynamicState{};
1208 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1209 dynamicState.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1210 dynamicState.pDynamicStates = dynamicStates.data();
1212 VkPipelineViewportStateCreateInfo viewportState{};
1213 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1214 viewportState.viewportCount = 1;
1215 viewportState.scissorCount = 1;
1217 VkPipelineRasterizationStateCreateInfo rasterizer{};
1218 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1219 rasterizer.depthClampEnable = VK_FALSE;
1220 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1221 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1222 rasterizer.lineWidth = 1.0f;
1223 rasterizer.cullMode = VK_CULL_MODE_NONE;
1224 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1225 rasterizer.depthBiasEnable = VK_FALSE;
1227 VkPipelineMultisampleStateCreateInfo multisampling{};
1228 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1229 multisampling.sampleShadingEnable = VK_FALSE;
1230 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1232 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1233 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1234 depthStencil.depthTestEnable = VK_FALSE;
1235 depthStencil.depthWriteEnable = VK_FALSE;
1237 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
1238 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1239 colorBlendAttachment.blendEnable = VK_TRUE;
1240 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
1241 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1242 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
1243 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
1244 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1245 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
1247 VkPipelineColorBlendStateCreateInfo colorBlending{};
1248 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1249 colorBlending.logicOpEnable = VK_FALSE;
1250 colorBlending.attachmentCount = 1;
1251 colorBlending.pAttachments = &colorBlendAttachment;
1253 VkPushConstantRange pushConstantRange{};
1254 pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1255 pushConstantRange.offset = 0;
1256 pushConstantRange.size =
sizeof(float) * 2;
1258 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
1259 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1260 pipelineLayoutInfo.setLayoutCount = 1;
1261 pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
1262 pipelineLayoutInfo.pushConstantRangeCount = 1;
1263 pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
1265 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo,
nullptr, &instancedPipelineLayout));
1267 VkGraphicsPipelineCreateInfo pipelineInfo{};
1268 VkPipelineRenderingCreateInfo renderingInfo{};
1269 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1270 renderingInfo.viewMask = 0;
1271 renderingInfo.colorAttachmentCount = 1;
1272 renderingInfo.pColorAttachmentFormats = &colorAttachmentFormat;
1273 if (depthAttachmentFormat != VK_FORMAT_UNDEFINED) {
1274 renderingInfo.depthAttachmentFormat = depthAttachmentFormat;
1277 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1278 pipelineInfo.pNext = &renderingInfo;
1279 pipelineInfo.stageCount = 2;
1280 pipelineInfo.pStages = shaderStages;
1281 pipelineInfo.pVertexInputState = &vertexInputInfo;
1282 pipelineInfo.pInputAssemblyState = &inputAssembly;
1283 pipelineInfo.pViewportState = &viewportState;
1284 pipelineInfo.pRasterizationState = &rasterizer;
1285 pipelineInfo.pMultisampleState = &multisampling;
1286 pipelineInfo.pDepthStencilState = &depthStencil;
1287 pipelineInfo.pColorBlendState = &colorBlending;
1288 pipelineInfo.pDynamicState = &dynamicState;
1289 pipelineInfo.layout = instancedPipelineLayout;
1290 pipelineInfo.renderPass = VK_NULL_HANDLE;
1291 pipelineInfo.subpass = 0;
1292 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
1294 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo,
nullptr, &instancedPipeline));
1296 vkDestroyShaderModule(device, vertModule,
nullptr);
1297 vkDestroyShaderModule(device, fragModule,
nullptr);
1300 void VK_Sprite::createCustomPipeline() {
1301 if (!hasCustomShader || fragmentShaderModule == VK_NULL_HANDLE)
1303 if (colorAttachmentFormat == VK_FORMAT_UNDEFINED || descriptorSetLayout == VK_NULL_HANDLE)
1306 if (customPipeline != VK_NULL_HANDLE) {
1307 vkDestroyPipeline(device, customPipeline,
nullptr);
1308 customPipeline = VK_NULL_HANDLE;
1310 if (customPipelineLayout != VK_NULL_HANDLE) {
1311 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
1312 customPipelineLayout = VK_NULL_HANDLE;
1315 std::string vertPath = vertexShaderPath.empty() ?
"sprite.vert.spv" : vertexShaderPath;
1316 auto vertShaderCode = readShaderFile(vertPath);
1319 VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
1320 vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1321 vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
1322 vertShaderStageInfo.module = vertShaderModule;
1323 vertShaderStageInfo.pName =
"main";
1325 VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
1326 fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1327 fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1328 fragShaderStageInfo.module = fragmentShaderModule;
1329 fragShaderStageInfo.pName =
"main";
1331 VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
1333 VkVertexInputBindingDescription bindingDescription{};
1334 bindingDescription.binding = 0;
1335 bindingDescription.stride =
sizeof(float) * 4;
1336 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1338 std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
1339 attributeDescriptions[0].binding = 0;
1340 attributeDescriptions[0].location = 0;
1341 attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
1342 attributeDescriptions[0].offset = 0;
1343 attributeDescriptions[1].binding = 0;
1344 attributeDescriptions[1].location = 1;
1345 attributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;
1346 attributeDescriptions[1].offset =
sizeof(float) * 2;
1348 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
1349 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1350 vertexInputInfo.vertexBindingDescriptionCount = 1;
1351 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
1352 vertexInputInfo.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributeDescriptions.size());
1353 vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
1355 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1356 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1357 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1358 inputAssembly.primitiveRestartEnable = VK_FALSE;
1360 std::vector<VkDynamicState> dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
1361 VkPipelineDynamicStateCreateInfo dynamicState{};
1362 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1363 dynamicState.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1364 dynamicState.pDynamicStates = dynamicStates.data();
1366 VkPipelineViewportStateCreateInfo viewportState{};
1367 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1368 viewportState.viewportCount = 1;
1369 viewportState.scissorCount = 1;
1371 VkPipelineRasterizationStateCreateInfo rasterizer{};
1372 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1373 rasterizer.depthClampEnable = VK_FALSE;
1374 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1375 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1376 rasterizer.lineWidth = 1.0f;
1377 rasterizer.cullMode = VK_CULL_MODE_NONE;
1378 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1379 rasterizer.depthBiasEnable = VK_FALSE;
1381 VkPipelineMultisampleStateCreateInfo multisampling{};
1382 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1383 multisampling.sampleShadingEnable = VK_FALSE;
1384 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1386 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1387 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1388 depthStencil.depthTestEnable = VK_FALSE;
1389 depthStencil.depthWriteEnable = VK_FALSE;
1391 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
1392 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1393 colorBlendAttachment.blendEnable = VK_TRUE;
1394 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
1395 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1396 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
1397 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
1398 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1399 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
1401 VkPipelineColorBlendStateCreateInfo colorBlending{};
1402 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1403 colorBlending.logicOpEnable = VK_FALSE;
1404 colorBlending.attachmentCount = 1;
1405 colorBlending.pAttachments = &colorBlendAttachment;
1407 VkPushConstantRange pushConstantRange{};
1408 pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
1409 pushConstantRange.offset = 0;
1410 pushConstantRange.size =
sizeof(float) * 12;
1412 VkDescriptorSetLayout layoutToUseForPipeline = extendedUBOEnabled ? extendedDescriptorSetLayout : descriptorSetLayout;
1414 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
1415 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1416 pipelineLayoutInfo.setLayoutCount = 1;
1417 pipelineLayoutInfo.pSetLayouts = &layoutToUseForPipeline;
1418 pipelineLayoutInfo.pushConstantRangeCount = 1;
1419 pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
1421 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo,
nullptr, &customPipelineLayout));
1423 VkGraphicsPipelineCreateInfo pipelineInfo{};
1424 VkPipelineRenderingCreateInfo renderingInfo{};
1425 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1426 renderingInfo.viewMask = 0;
1427 renderingInfo.colorAttachmentCount = 1;
1428 renderingInfo.pColorAttachmentFormats = &colorAttachmentFormat;
1429 if (depthAttachmentFormat != VK_FORMAT_UNDEFINED) {
1430 renderingInfo.depthAttachmentFormat = depthAttachmentFormat;
1433 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1434 pipelineInfo.pNext = &renderingInfo;
1435 pipelineInfo.stageCount = 2;
1436 pipelineInfo.pStages = shaderStages;
1437 pipelineInfo.pVertexInputState = &vertexInputInfo;
1438 pipelineInfo.pInputAssemblyState = &inputAssembly;
1439 pipelineInfo.pViewportState = &viewportState;
1440 pipelineInfo.pRasterizationState = &rasterizer;
1441 pipelineInfo.pMultisampleState = &multisampling;
1442 pipelineInfo.pDepthStencilState = &depthStencil;
1443 pipelineInfo.pColorBlendState = &colorBlending;
1444 pipelineInfo.pDynamicState = &dynamicState;
1445 pipelineInfo.layout = customPipelineLayout;
1446 pipelineInfo.renderPass = VK_NULL_HANDLE;
1447 pipelineInfo.subpass = 0;
1448 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
1450 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo,
nullptr, &customPipeline));
1452 vkDestroyShaderModule(device, vertShaderModule,
nullptr);
1456 if (!hasCustomShader || fragmentShaderModule == VK_NULL_HANDLE)
1458 createCustomPipeline();
1459 std::cout <<
"mxvk: Pipeline rebuilt\n";
1463 if (path == fragmentShaderPath && fragmentShaderModule != VK_NULL_HANDLE) {
1467 if (customPipeline != VK_NULL_HANDLE) {
1468 vkDestroyPipeline(device, customPipeline,
nullptr);
1469 customPipeline = VK_NULL_HANDLE;
1471 if (customPipelineLayout != VK_NULL_HANDLE) {
1472 vkDestroyPipelineLayout(device, customPipelineLayout,
nullptr);
1473 customPipelineLayout = VK_NULL_HANDLE;
1475 if (fragmentShaderModule != VK_NULL_HANDLE) {
1476 vkDestroyShaderModule(device, fragmentShaderModule,
nullptr);
1477 fragmentShaderModule = VK_NULL_HANDLE;
1480 fragmentShaderPath = path;
1481 hasCustomShader =
false;
1483 if (fragmentShaderPath.empty()) {
1487 const auto shaderCode = readShaderFile(fragmentShaderPath);
1489 hasCustomShader =
true;
1491 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
1492 createCustomPipeline();
1496 void VK_Sprite::destroyComputePipeline() {
1497 if (computePipeline != VK_NULL_HANDLE) {
1498 std::cout <<
"vk: destroying sprite compute pipeline\n";
1499 vkDestroyPipeline(device, computePipeline,
nullptr);
1500 computePipeline = VK_NULL_HANDLE;
1502 if (computePipelineLayout != VK_NULL_HANDLE) {
1503 std::cout <<
"vk: destroying sprite compute pipeline layout\n";
1504 vkDestroyPipelineLayout(device, computePipelineLayout,
nullptr);
1505 computePipelineLayout = VK_NULL_HANDLE;
1509 void VK_Sprite::createComputePipeline() {
1510 if (computeShaderModule == VK_NULL_HANDLE || extendedDescriptorSetLayout == VK_NULL_HANDLE) {
1514 destroyComputePipeline();
1515 VkPipelineLayoutCreateInfo layoutInfo{};
1516 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1517 layoutInfo.setLayoutCount = 1;
1518 layoutInfo.pSetLayouts = &extendedDescriptorSetLayout;
1519 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &layoutInfo,
nullptr, &computePipelineLayout));
1521 VkPipelineShaderStageCreateInfo stageInfo{};
1522 stageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1523 stageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT;
1524 stageInfo.module = computeShaderModule;
1525 stageInfo.pName =
"main";
1527 VkComputePipelineCreateInfo pipelineInfo{};
1528 pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
1529 pipelineInfo.stage = stageInfo;
1530 pipelineInfo.layout = computePipelineLayout;
1531 VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &pipelineInfo,
nullptr, &computePipeline));
1532 std::cout <<
"mxvk: Compute pipeline rebuilt\n";
1536 if (path.empty() || localSizeX == 0 || localSizeY == 0 || localSizeZ == 0) {
1537 throw mxvk::Exception(
"VKSprite::enableComputeShader requires a shader and positive local size");
1540 vkDeviceWaitIdle(device);
1541 destroyComputePipeline();
1542 if (computeShaderModule != VK_NULL_HANDLE) {
1543 vkDestroyShaderModule(device, computeShaderModule,
nullptr);
1544 computeShaderModule = VK_NULL_HANDLE;
1547 computeLocalSizeX = localSizeX;
1548 computeLocalSizeY = localSizeY;
1550 if (!extendedUBOEnabled) {
1552 createComputePipeline();
1554 recreateExtendedDescriptorLayout();
1559 if (computePipeline == VK_NULL_HANDLE || computePipelineLayout == VK_NULL_HANDLE || inputView == VK_NULL_HANDLE || outputView == VK_NULL_HANDLE || width == 0 || height == 0) {
1560 throw mxvk::Exception(
"VKSprite::dispatchCompute received an incomplete compute pass");
1564 if (computeOutputImageView != outputView) {
1565 if (extendedDescriptorPool != VK_NULL_HANDLE) {
1566 vkDeviceWaitIdle(device);
1567 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
1568 extendedDescriptorPool = VK_NULL_HANDLE;
1569 extendedDescriptorSet = VK_NULL_HANDLE;
1571 computeOutputImageView = outputView;
1573 updateExtendedUBO();
1574 if (extendedDescriptorSet == VK_NULL_HANDLE) {
1575 createExtendedDescriptorSet();
1577 if (extendedDescriptorSet == VK_NULL_HANDLE) {
1578 throw mxvk::Exception(
"VKSprite::dispatchCompute could not create its descriptor set");
1581 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline);
1582 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 0, 1, &extendedDescriptorSet, 0,
nullptr);
1583 vkCmdDispatch(cmdBuffer, (width + computeLocalSizeX - 1U) / computeLocalSizeX, (height + computeLocalSizeY - 1U) / computeLocalSizeY, 1U);
1587 if (!instancingEnabled || instanceVertPath.empty() || instanceFragPath.empty())
1589 createInstancedPipeline(instanceVertPath, instanceFragPath);
1592 void VK_Sprite::createQuadBuffer() {
1593 if (quadBufferCreated)
1596 SpriteVertex vertices[] = {{{0.0f, 0.0f}, {0.0f, 0.0f}}, {{1.0f, 0.0f}, {1.0f, 0.0f}}, {{1.0f, 1.0f}, {1.0f, 1.0f}}, {{0.0f, 1.0f}, {0.0f, 1.0f}}};
1597 uint16_t indices[] = {0, 1, 2, 0, 2, 3};
1599 VkDeviceSize vertexSize =
sizeof(vertices);
1600 createBuffer(vertexSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, quadVertexBuffer, quadVertexBufferMemory);
1603 VK_CHECK_RESULT(vkMapMemory(device, quadVertexBufferMemory, 0, vertexSize, 0, &data));
1604 memcpy(data, vertices, vertexSize);
1605 vkUnmapMemory(device, quadVertexBufferMemory);
1607 VkDeviceSize indexSize =
sizeof(indices);
1608 createBuffer(indexSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, quadIndexBuffer, quadIndexBufferMemory);
1610 VK_CHECK_RESULT(vkMapMemory(device, quadIndexBufferMemory, 0, indexSize, 0, &data));
1611 memcpy(data, indices, indexSize);
1612 vkUnmapMemory(device, quadIndexBufferMemory);
1614 quadBufferCreated =
true;
1624 std::cout << std::format(
"mxvk: Loaded PNG: {}\n", pngPath);
1629 throw mxvk::Exception(
"VKSprite::loadSprite called with null surface");
1631 if (spriteLoaded || spriteImage != VK_NULL_HANDLE || fragmentShaderModule != VK_NULL_HANDLE) {
1632 destroySpriteResources();
1634 SDL_Surface *rgbaSurface = convertToRGBA(
surface);
1638 spriteWidth = rgbaSurface->w;
1639 spriteHeight = rgbaSurface->h;
1640 createSpriteTexture(rgbaSurface);
1641 SDL_DestroySurface(rgbaSurface);
1644 if (!fragmentShaderPath.empty()) {
1645 auto shaderCode = readShaderFile(fragmentShaderPath);
1647 hasCustomShader =
true;
1648 this->fragmentShaderPath = fragmentShaderPath;
1650 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
1651 createCustomPipeline();
1654 spriteLoaded =
true;
1655 std::cout << std::format(
"mxvk: Loaded surface texture: {}x{}\n", spriteWidth, spriteHeight);
1658 void VK_Sprite::createEmptySprite(
int width,
int height,
const std::string &vertexShaderPath,
const std::string &fragmentShaderPath) { createEmptySpriteWithFormat(width, height, VK_FORMAT_R8G8B8A8_UNORM, 4, vertexShaderPath, fragmentShaderPath); }
1660 void VK_Sprite::createEmptySpriteRgba16(
int width,
int height,
const std::string &vertexShaderPath,
const std::string &fragmentShaderPath) { createEmptySpriteWithFormat(width, height, VK_FORMAT_R16G16B16A16_UNORM, 8, vertexShaderPath, fragmentShaderPath); }
1662 void VK_Sprite::createEmptySpriteWithFormat(
int width,
int height, VkFormat format, uint32_t bytesPerPixel,
const std::string &vertexShaderPath,
const std::string &fragmentShaderPath) {
1663 if (width <= 0 || height <= 0) {
1664 throw mxvk::Exception(
"VKSprite::createEmptySprite invalid dimensions");
1666 if (bytesPerPixel == 0) {
1667 throw mxvk::Exception(
"VKSprite::createEmptySprite invalid pixel size");
1669 if (spriteLoaded || spriteImage != VK_NULL_HANDLE || fragmentShaderModule != VK_NULL_HANDLE) {
1670 destroySpriteResources();
1672 spriteWidth = width;
1673 spriteHeight = height;
1674 spriteImageFormat = format;
1675 spriteBytesPerPixel = bytesPerPixel;
1677 if (!vertexShaderPath.empty()) {
1682 if (format == VK_FORMAT_R8G8B8A8_UNORM) {
1684 createCudaExportableImage(width, height, 1, spriteImage, spriteImageMemory, cudaExportMemorySize);
1685 cudaInteropUnavailableLogged =
false;
1686 }
catch (
const std::exception &ex) {
1687 std::cout << std::format(
"mxvk: CUDA exportable sprite image unavailable: {}; using standard Vulkan image\n", ex.what());
1688 createImage(width, height, format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
1691 createImage(width, height, format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
1694 createImage(width, height, format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
1697 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1698 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
1699 VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) * height * bytesPerPixel;
1701 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
1704 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
1705 memset(data, 0, imageSize);
1706 vkUnmapMemory(device, stagingMemory);
1708 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1709 copyBufferToImage(stagingBuffer, spriteImage, width, height);
1710 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1712 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1715 vkDestroyBuffer(device, stagingBuffer,
nullptr);
1716 vkFreeMemory(device, stagingMemory,
nullptr);
1718 spriteImageView = createImageView(spriteImage, format);
1721 createDescriptorPool();
1723 createStagingResources(imageSize);
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();
1736 spriteLoaded =
true;
1737 std::cout << std::format(
"mxvk: Created empty sprite: {}x{} ({})\n", spriteWidth, spriteHeight, format == VK_FORMAT_R16G16B16A16_UNORM ?
"RGBA16 UNORM" :
"RGBA8 UNORM");
1742 throw mxvk::Exception(
"VKSprite::updateTexture called with null surface");
1744 if (!spriteLoaded) {
1745 throw mxvk::Exception(
"VKSprite::updateTexture called before sprite was loaded");
1747 SDL_Surface *rgbaSurface = convertToRGBA(
surface);
1749 throw mxvk::Exception(
"Failed to convert surface to RGBA in updateTexture");
1751 if (rgbaSurface->w == spriteWidth && rgbaSurface->h == spriteHeight) {
1753 if (updateTextureCudaHost(rgbaSurface->pixels,
static_cast<uint32_t
>(rgbaSurface->w),
static_cast<uint32_t
>(rgbaSurface->h),
static_cast<uint32_t
>(rgbaSurface->pitch))) {
1754 SDL_DestroySurface(rgbaSurface);
1758 updateSpriteTexture(rgbaSurface->pixels, rgbaSurface->w, rgbaSurface->h);
1760 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
1761 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1764 destroyCudaInterop();
1766 destroyTextureDescriptorPools();
1767 if (spriteImageView != VK_NULL_HANDLE) {
1768 vkDestroyImageView(device, spriteImageView,
nullptr);
1769 spriteImageView = VK_NULL_HANDLE;
1771 if (spriteImage != VK_NULL_HANDLE) {
1772 vkDestroyImage(device, spriteImage,
nullptr);
1773 spriteImage = VK_NULL_HANDLE;
1775 if (spriteImageMemory != VK_NULL_HANDLE) {
1776 vkFreeMemory(device, spriteImageMemory,
nullptr);
1777 spriteImageMemory = VK_NULL_HANDLE;
1779 spriteWidth = rgbaSurface->w;
1780 spriteHeight = rgbaSurface->h;
1781 createSpriteTexture(rgbaSurface);
1782 createDescriptorPool();
1784 SDL_DestroySurface(rgbaSurface);
1789 throw mxvk::Exception(
"VKSprite::updateTexture called with null pixel data");
1791 if (!spriteLoaded) {
1792 throw mxvk::Exception(
"VKSprite::updateTexture called before sprite was loaded");
1794 if (width <= 0 || height <= 0) {
1797 if (spriteImageFormat != VK_FORMAT_R8G8B8A8_UNORM || spriteBytesPerPixel != 4) {
1798 throw mxvk::Exception(
"VKSprite::updateTexture cannot update an RGBA16 sprite; use "
1799 "updateTextureRgba16");
1801 int srcPitch = (pitch > 0) ? pitch : width * 4;
1802 if (width == spriteWidth && height == spriteHeight && srcPitch == width * 4) {
1804 if (updateTextureCudaHost(pixels,
static_cast<uint32_t
>(width),
static_cast<uint32_t
>(height),
static_cast<uint32_t
>(srcPitch))) {
1808 updateSpriteTexture(pixels, width, height);
1809 }
else if (width == spriteWidth && height == spriteHeight) {
1811 if (updateTextureCudaHost(pixels,
static_cast<uint32_t
>(width),
static_cast<uint32_t
>(height),
static_cast<uint32_t
>(srcPitch))) {
1815 std::vector<uint8_t> packed(width * height * 4);
1816 const uint8_t *src =
static_cast<const uint8_t *
>(pixels);
1817 for (
int row = 0; row < height; ++row) {
1818 memcpy(packed.data() + row * width * 4, src + row * srcPitch, width * 4);
1820 updateSpriteTexture(packed.data(), width, height);
1822 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
1823 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1826 destroyCudaInterop();
1828 destroyTextureDescriptorPools();
1829 if (spriteImageView != VK_NULL_HANDLE) {
1830 vkDestroyImageView(device, spriteImageView,
nullptr);
1831 spriteImageView = VK_NULL_HANDLE;
1833 if (spriteImage != VK_NULL_HANDLE) {
1834 vkDestroyImage(device, spriteImage,
nullptr);
1835 spriteImage = VK_NULL_HANDLE;
1837 if (spriteImageMemory != VK_NULL_HANDLE) {
1838 vkFreeMemory(device, spriteImageMemory,
nullptr);
1839 spriteImageMemory = VK_NULL_HANDLE;
1841 spriteWidth = width;
1842 spriteHeight = height;
1843 std::vector<uint8_t> packed;
1844 const void *texData = pixels;
1845 if (srcPitch != width * 4) {
1846 packed.resize(width * height * 4);
1847 const uint8_t *src =
static_cast<const uint8_t *
>(pixels);
1848 for (
int row = 0; row < height; ++row) {
1849 memcpy(packed.data() + row * width * 4, src + row * srcPitch, width * 4);
1851 texData = packed.data();
1854 SDL_Surface *tmpSurface = SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_RGBA32,
const_cast<void *
>(texData), width * 4);
1856 throw mxvk::Exception(
"VKSprite::updateTexture failed to create temp surface");
1858 createSpriteTexture(tmpSurface);
1859 SDL_DestroySurface(tmpSurface);
1860 createDescriptorPool();
1865 if (pixels ==
nullptr) {
1866 throw mxvk::Exception(
"VKSprite::updateTextureRgba16 called with null pixel data");
1868 if (!spriteLoaded || spriteImageFormat != VK_FORMAT_R16G16B16A16_UNORM || spriteBytesPerPixel != 8) {
1869 throw mxvk::Exception(
"VKSprite::updateTextureRgba16 requires an RGBA16 sprite");
1871 if (width != spriteWidth || height != spriteHeight || width <= 0 || height <= 0) {
1872 throw mxvk::Exception(
"VKSprite::updateTextureRgba16 dimensions do not match");
1875 const int rowBytes = width * 8;
1876 const int sourcePitch = pitch > 0 ? pitch : rowBytes;
1877 if (sourcePitch < rowBytes) {
1878 throw mxvk::Exception(
"VKSprite::updateTextureRgba16 pitch is too small");
1880 if (sourcePitch == rowBytes) {
1881 updateSpriteTexture(pixels,
static_cast<uint32_t
>(width),
static_cast<uint32_t
>(height));
1885 std::vector<uint16_t> packed(
static_cast<size_t>(width) *
static_cast<size_t>(height) * 4U);
1886 const auto *source =
reinterpret_cast<const uint8_t *
>(pixels);
1887 auto *destination =
reinterpret_cast<uint8_t *
>(packed.data());
1888 for (
int row = 0; row < height; ++row) {
1889 std::memcpy(destination +
static_cast<size_t>(row) * rowBytes, source +
static_cast<size_t>(row) * sourcePitch,
static_cast<size_t>(rowBytes));
1891 updateSpriteTexture(packed.data(),
static_cast<uint32_t
>(width),
static_cast<uint32_t
>(height));
1894 void VK_Sprite::updateSpriteTexture(
const void *pixels, uint32_t width, uint32_t height) {
1895 VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) * height * spriteBytesPerPixel;
1897 createStagingResources(imageSize);
1898 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
1900 memcpy(persistentStagingMapped, pixels, imageSize);
1902 VkCommandBufferBeginInfo beginInfo{};
1903 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1904 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1906 VkImageMemoryBarrier barrier{};
1907 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1908 VkImageLayout oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1910 if (cudaImageLayout != VK_IMAGE_LAYOUT_UNDEFINED) {
1911 oldLayout = cudaImageLayout;
1914 barrier.oldLayout = oldLayout;
1915 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
1916 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1917 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1918 barrier.image = spriteImage;
1919 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1920 barrier.subresourceRange.baseMipLevel = 0;
1921 barrier.subresourceRange.levelCount = 1;
1922 barrier.subresourceRange.baseArrayLayer = 0;
1923 barrier.subresourceRange.layerCount = 1;
1924 barrier.srcAccessMask = (oldLayout == VK_IMAGE_LAYOUT_GENERAL) ? VK_ACCESS_MEMORY_WRITE_BIT : VK_ACCESS_SHADER_READ_BIT;
1925 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1926 const VkPipelineStageFlags srcStage = (oldLayout == VK_IMAGE_LAYOUT_GENERAL) ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT : VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1927 vkCmdPipelineBarrier(uploadCmdBuffer, srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1929 VkBufferImageCopy region{};
1930 region.bufferOffset = 0;
1931 region.bufferRowLength = 0;
1932 region.bufferImageHeight = 0;
1933 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1934 region.imageSubresource.mipLevel = 0;
1935 region.imageSubresource.baseArrayLayer = 0;
1936 region.imageSubresource.layerCount = 1;
1937 region.imageOffset = {0, 0, 0};
1938 region.imageExtent = {width, height, 1};
1939 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer, spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
1941 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
1942 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1943 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1944 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1945 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1949 VkSubmitInfo submitInfo{};
1950 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1951 submitInfo.commandBufferCount = 1;
1952 submitInfo.pCommandBuffers = &uploadCmdBuffer;
1953 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
1955 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1956 cudaImageNeedsShaderBarrier =
false;
1961 void VK_Sprite::destroyCudaInterop() {
1962 if (cudaInteropEnabled || cudaExternalMemory !=
nullptr || cudaMipmappedArray !=
nullptr) {
1963 std::cout <<
"mxvk: CUDA interop: destroying imported Vulkan texture resources\n";
1965 if (cudaMipmappedArray !=
nullptr) {
1966 cudaFreeMipmappedArray(cudaMipmappedArray);
1967 cudaMipmappedArray =
nullptr;
1968 cudaArray =
nullptr;
1970 if (cudaExternalMemory !=
nullptr) {
1971 cudaDestroyExternalMemory(cudaExternalMemory);
1972 cudaExternalMemory =
nullptr;
1974 cudaInteropEnabled =
false;
1975 cudaImageNeedsShaderBarrier =
false;
1976 cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1977 cudaExportMemorySize = 0;
1978 cudaUploadLogged =
false;
1979 cudaWriteTransitionLogged =
false;
1980 cudaSampleBarrierLogged =
false;
1983 void VK_Sprite::createCudaExportableImage(uint32_t width, uint32_t height, uint32_t arrayLayers, VkImage &image, VkDeviceMemory &imageMemory, VkDeviceSize &exportMemorySize) {
1984 std::cout << std::format(
"mxvk: CUDA interop init: requesting exportable Vulkan image "
1985 "{}x{}x{} RGBA8 OPAQUE_FD\n",
1989 if (image != VK_NULL_HANDLE) {
1990 vkDestroyImage(device, image,
nullptr);
1991 image = VK_NULL_HANDLE;
1993 if (imageMemory != VK_NULL_HANDLE) {
1994 vkFreeMemory(device, imageMemory,
nullptr);
1995 imageMemory = VK_NULL_HANDLE;
1998 VkExternalMemoryImageCreateInfo externalImageInfo{};
1999 externalImageInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
2000 externalImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
2002 VkImageCreateInfo imageInfo{};
2003 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2004 imageInfo.pNext = &externalImageInfo;
2005 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2006 imageInfo.extent.width = width;
2007 imageInfo.extent.height = height;
2008 imageInfo.extent.depth = 1;
2009 imageInfo.mipLevels = 1;
2010 imageInfo.arrayLayers = arrayLayers;
2011 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
2012 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
2013 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2014 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
2015 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2016 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2020 VkMemoryRequirements memRequirements{};
2021 vkGetImageMemoryRequirements(device, image, &memRequirements);
2023 VkExportMemoryAllocateInfo exportMemoryInfo{};
2024 exportMemoryInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
2025 exportMemoryInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
2027 VkMemoryAllocateInfo allocInfo{};
2028 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
2029 allocInfo.pNext = &exportMemoryInfo;
2030 allocInfo.allocationSize = memRequirements.size;
2033 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
2034 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &imageMemory));
2036 exportMemorySize = memRequirements.size;
2037 std::cout << std::format(
"mxvk: CUDA interop init: exportable Vulkan image allocated (memorySize={} bytes, memoryType={})\n",
static_cast<unsigned long long>(exportMemorySize), allocInfo.memoryTypeIndex);
2039 if (imageMemory != VK_NULL_HANDLE) {
2040 vkFreeMemory(device, imageMemory,
nullptr);
2041 imageMemory = VK_NULL_HANDLE;
2043 if (image != VK_NULL_HANDLE) {
2044 vkDestroyImage(device, image,
nullptr);
2045 image = VK_NULL_HANDLE;
2047 exportMemorySize = 0;
2052 bool VK_Sprite::ensureCudaInterop() {
2053 if (cudaInteropEnabled) {
2056 if (spriteImage == VK_NULL_HANDLE || spriteImageMemory == VK_NULL_HANDLE || cudaExportMemorySize == 0) {
2057 if (!cudaInteropUnavailableLogged) {
2058 std::cout <<
"mxvk: CUDA interop init: sprite image is not exportable; using CPU/pinned fallback\n";
2059 cudaInteropUnavailableLogged =
true;
2063 if (vkGetMemoryFdKHR ==
nullptr) {
2064 if (!cudaInteropUnavailableLogged) {
2065 std::cout <<
"mxvk: CUDA interop init: vkGetMemoryFdKHR was not loaded; using CPU/pinned fallback\n";
2066 cudaInteropUnavailableLogged =
true;
2071 VkMemoryGetFdInfoKHR fdInfo{};
2072 fdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
2073 fdInfo.memory = spriteImageMemory;
2074 fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
2077 const VkResult fdResult = vkGetMemoryFdKHR(device, &fdInfo, &memoryFd);
2078 if (fdResult != VK_SUCCESS) {
2079 if (!cudaInteropUnavailableLogged) {
2080 std::cout << std::format(
"mxvk: CUDA interop init: vkGetMemoryFdKHR failed ({})\n",
static_cast<int>(fdResult));
2081 cudaInteropUnavailableLogged =
true;
2085 std::cout << std::format(
"mxvk: CUDA interop init: exported Vulkan image memory fd={}\n", memoryFd);
2087 cudaExternalMemoryHandleDesc externalMemoryDesc{};
2088 externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueFd;
2089 externalMemoryDesc.handle.fd = memoryFd;
2090 externalMemoryDesc.size = cudaExportMemorySize;
2092 cudaError_t cudaResult = cudaImportExternalMemory(&cudaExternalMemory, &externalMemoryDesc);
2093 if (cudaResult != cudaSuccess) {
2095 if (!cudaInteropUnavailableLogged) {
2096 std::cout << std::format(
"mxvk: CUDA interop init: cudaImportExternalMemory failed: {}\n", cudaGetErrorString(cudaResult));
2097 cudaInteropUnavailableLogged =
true;
2099 cudaExternalMemory =
nullptr;
2102 std::cout << std::format(
"mxvk: CUDA interop init: imported external memory into CUDA ({} bytes)\n",
static_cast<unsigned long long>(cudaExportMemorySize));
2104 cudaExternalMemoryMipmappedArrayDesc arrayDesc{};
2105 arrayDesc.offset = 0;
2106 arrayDesc.formatDesc = cudaCreateChannelDesc<uchar4>();
2107 arrayDesc.extent = make_cudaExtent(
static_cast<size_t>(spriteWidth),
static_cast<size_t>(spriteHeight), 0);
2108 arrayDesc.flags = cudaArrayColorAttachment;
2109 arrayDesc.numLevels = 1;
2111 cudaResult = cudaExternalMemoryGetMappedMipmappedArray(&cudaMipmappedArray, cudaExternalMemory, &arrayDesc);
2112 if (cudaResult != cudaSuccess) {
2113 if (!cudaInteropUnavailableLogged) {
2114 std::cout << std::format(
"mxvk: CUDA interop init: cudaExternalMemoryGetMappedMipmappedArray failed: {}\n", cudaGetErrorString(cudaResult));
2115 cudaInteropUnavailableLogged =
true;
2117 destroyCudaInterop();
2120 std::cout << std::format(
"mxvk: CUDA interop init: mapped CUDA mipmapped array {}x{} uchar4\n", spriteWidth, spriteHeight);
2122 cudaResult = cudaGetMipmappedArrayLevel(&cudaArray, cudaMipmappedArray, 0);
2123 if (cudaResult != cudaSuccess) {
2124 if (!cudaInteropUnavailableLogged) {
2125 std::cout << std::format(
"mxvk: CUDA interop init: cudaGetMipmappedArrayLevel failed: {}\n", cudaGetErrorString(cudaResult));
2126 cudaInteropUnavailableLogged =
true;
2128 destroyCudaInterop();
2132 cudaInteropEnabled =
true;
2133 std::cout <<
"mxvk: CUDA interop init: direct CUDA-to-Vulkan texture upload is ready\n";
2137 void VK_Sprite::destroyCudaHistoryInterop() {
2138 if (cudaHistoryInteropEnabled || cudaHistoryExternalMemory !=
nullptr || cudaHistoryMipmappedArray !=
nullptr) {
2139 std::cout <<
"mxvk: CUDA interop: destroying imported history "
2140 "texture resources\n";
2142 if (cudaHistoryMipmappedArray !=
nullptr) {
2143 cudaFreeMipmappedArray(cudaHistoryMipmappedArray);
2144 cudaHistoryMipmappedArray =
nullptr;
2145 cudaHistoryArray =
nullptr;
2147 if (cudaHistoryExternalMemory !=
nullptr) {
2148 cudaDestroyExternalMemory(cudaHistoryExternalMemory);
2149 cudaHistoryExternalMemory =
nullptr;
2151 cudaHistoryInteropEnabled =
false;
2152 cudaHistoryExportMemorySize = 0;
2153 cudaHistoryUploadLogged =
false;
2156 bool VK_Sprite::ensureCudaHistoryInterop() {
2157 if (cudaHistoryInteropEnabled) {
2160 if (historyImage == VK_NULL_HANDLE || historyImageMemory == VK_NULL_HANDLE || cudaHistoryExportMemorySize == 0) {
2161 if (!cudaHistoryInteropUnavailableLogged) {
2162 std::cout <<
"mxvk: CUDA history interop: history image is not "
2164 cudaHistoryInteropUnavailableLogged =
true;
2168 if (vkGetMemoryFdKHR ==
nullptr) {
2169 if (!cudaHistoryInteropUnavailableLogged) {
2170 std::cout <<
"mxvk: CUDA history interop: vkGetMemoryFdKHR was "
2172 cudaHistoryInteropUnavailableLogged =
true;
2177 VkMemoryGetFdInfoKHR fdInfo{};
2178 fdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
2179 fdInfo.memory = historyImageMemory;
2180 fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
2183 const VkResult fdResult = vkGetMemoryFdKHR(device, &fdInfo, &memoryFd);
2184 if (fdResult != VK_SUCCESS) {
2185 if (!cudaHistoryInteropUnavailableLogged) {
2186 std::cout << std::format(
"mxvk: CUDA history interop: vkGetMemoryFdKHR failed "
2188 static_cast<int>(fdResult));
2189 cudaHistoryInteropUnavailableLogged =
true;
2194 cudaExternalMemoryHandleDesc externalMemoryDesc{};
2195 externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueFd;
2196 externalMemoryDesc.handle.fd = memoryFd;
2197 externalMemoryDesc.size = cudaHistoryExportMemorySize;
2199 cudaError_t cudaResult = cudaImportExternalMemory(&cudaHistoryExternalMemory, &externalMemoryDesc);
2200 if (cudaResult != cudaSuccess) {
2202 if (!cudaHistoryInteropUnavailableLogged) {
2203 std::cout << std::format(
"mxvk: CUDA history interop: import failed: {}\n", cudaGetErrorString(cudaResult));
2204 cudaHistoryInteropUnavailableLogged =
true;
2206 cudaHistoryExternalMemory =
nullptr;
2210 cudaExternalMemoryMipmappedArrayDesc arrayDesc{};
2211 arrayDesc.offset = 0;
2212 arrayDesc.formatDesc = cudaCreateChannelDesc<uchar4>();
2213 arrayDesc.extent = make_cudaExtent(
static_cast<size_t>(historyWidth),
static_cast<size_t>(historyHeight),
static_cast<size_t>(historyLayers));
2214 arrayDesc.flags = cudaArrayColorAttachment | cudaArrayLayered;
2215 arrayDesc.numLevels = 1;
2217 cudaResult = cudaExternalMemoryGetMappedMipmappedArray(&cudaHistoryMipmappedArray, cudaHistoryExternalMemory, &arrayDesc);
2218 if (cudaResult != cudaSuccess) {
2219 if (!cudaHistoryInteropUnavailableLogged) {
2220 std::cout << std::format(
"mxvk: CUDA history interop: array mapping failed: {}\n", cudaGetErrorString(cudaResult));
2221 cudaHistoryInteropUnavailableLogged =
true;
2223 destroyCudaHistoryInterop();
2227 cudaResult = cudaGetMipmappedArrayLevel(&cudaHistoryArray, cudaHistoryMipmappedArray, 0);
2228 if (cudaResult != cudaSuccess) {
2229 if (!cudaHistoryInteropUnavailableLogged) {
2230 std::cout << std::format(
"mxvk: CUDA history interop: array lookup failed: {}\n", cudaGetErrorString(cudaResult));
2231 cudaHistoryInteropUnavailableLogged =
true;
2233 destroyCudaHistoryInterop();
2237 cudaHistoryInteropEnabled =
true;
2238 cudaHistoryInteropUnavailableLogged =
false;
2239 std::cout << std::format(
"mxvk: CUDA history interop: direct {}-layer upload is ready\n", historyLayers);
2243 void VK_Sprite::transitionCudaHistoryLayer(VkImageLayout oldLayout, VkImageLayout newLayout, VkAccessFlags sourceAccess, VkAccessFlags destinationAccess, VkPipelineStageFlags sourceStage, VkPipelineStageFlags destinationStage) {
2244 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2245 VkImageMemoryBarrier barrier{};
2246 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2247 barrier.oldLayout = oldLayout;
2248 barrier.newLayout = newLayout;
2249 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2250 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2251 barrier.image = historyImage;
2252 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2253 barrier.subresourceRange.baseMipLevel = 0;
2254 barrier.subresourceRange.levelCount = 1;
2255 barrier.subresourceRange.baseArrayLayer = historyHead;
2256 barrier.subresourceRange.layerCount = 1;
2257 barrier.srcAccessMask = sourceAccess;
2258 barrier.dstAccessMask = destinationAccess;
2259 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
2260 endSingleTimeCommands(commandBuffer);
2263 bool VK_Sprite::transitionCudaImageForWrite() {
2264 if (cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) {
2268 const VkImageLayout oldLayout = (cudaImageLayout == VK_IMAGE_LAYOUT_UNDEFINED) ? VK_IMAGE_LAYOUT_UNDEFINED : cudaImageLayout;
2269 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2271 VkImageMemoryBarrier barrier{};
2272 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2273 barrier.oldLayout = oldLayout;
2274 barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
2275 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2276 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2277 barrier.image = spriteImage;
2278 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2279 barrier.subresourceRange.baseMipLevel = 0;
2280 barrier.subresourceRange.levelCount = 1;
2281 barrier.subresourceRange.baseArrayLayer = 0;
2282 barrier.subresourceRange.layerCount = 1;
2283 barrier.srcAccessMask = (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) ? VK_ACCESS_SHADER_READ_BIT : 0;
2284 barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
2286 const VkPipelineStageFlags srcStage = (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) ? VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2287 vkCmdPipelineBarrier(commandBuffer, srcStage, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
2288 endSingleTimeCommands(commandBuffer);
2290 cudaImageLayout = VK_IMAGE_LAYOUT_GENERAL;
2291 if (!cudaWriteTransitionLogged) {
2292 std::cout <<
"mxvk: CUDA interop sync: Vulkan image transitions to GENERAL before CUDA writes\n";
2293 cudaWriteTransitionLogged =
true;
2298 bool VK_Sprite::transitionCudaImageForShaderRead() {
2299 if (cudaImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && !cudaImageNeedsShaderBarrier) {
2303 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2304 VkImageMemoryBarrier barrier{};
2305 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2306 barrier.oldLayout = cudaImageLayout;
2307 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2308 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2309 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2310 barrier.image = spriteImage;
2311 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2312 barrier.subresourceRange.baseMipLevel = 0;
2313 barrier.subresourceRange.levelCount = 1;
2314 barrier.subresourceRange.baseArrayLayer = 0;
2315 barrier.subresourceRange.layerCount = 1;
2316 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
2317 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
2319 vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
2320 endSingleTimeCommands(commandBuffer);
2322 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2323 cudaImageNeedsShaderBarrier =
false;
2324 if (!cudaSampleBarrierLogged) {
2325 std::cout <<
"mxvk: CUDA interop sync: Vulkan transitions GENERAL -> SHADER_READ_ONLY before sampling\n";
2326 cudaSampleBarrierLogged =
true;
2331 void VK_Sprite::recordCudaReadyBarrier(VkCommandBuffer cmdBuffer) {
2332 if (!cudaImageNeedsShaderBarrier) {
2337 transitionCudaImageForShaderRead();
2340 bool VK_Sprite::updateTextureCuda(
const cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream) {
2341 if (!spriteLoaded) {
2344 if (spriteImageFormat != VK_FORMAT_R8G8B8A8_UNORM || spriteBytesPerPixel != 4 || rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols <= 0 || rgba.rows <= 0) {
2347 if (rgba.cols != spriteWidth || rgba.rows != spriteHeight || spriteImage == VK_NULL_HANDLE || spriteImageMemory == VK_NULL_HANDLE || spriteImageView == VK_NULL_HANDLE || cudaExportMemorySize == 0) {
2348 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
2349 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
2351 vkDeviceWaitIdle(device);
2352 destroyCudaInterop();
2353 destroyTextureDescriptorPools();
2354 if (spriteImageView != VK_NULL_HANDLE) {
2355 vkDestroyImageView(device, spriteImageView,
nullptr);
2356 spriteImageView = VK_NULL_HANDLE;
2358 if (spriteImage != VK_NULL_HANDLE) {
2359 vkDestroyImage(device, spriteImage,
nullptr);
2360 spriteImage = VK_NULL_HANDLE;
2362 if (spriteImageMemory != VK_NULL_HANDLE) {
2363 vkFreeMemory(device, spriteImageMemory,
nullptr);
2364 spriteImageMemory = VK_NULL_HANDLE;
2367 spriteWidth = rgba.cols;
2368 spriteHeight = rgba.rows;
2370 createCudaExportableImage(
static_cast<uint32_t
>(spriteWidth),
static_cast<uint32_t
>(spriteHeight), 1, spriteImage, spriteImageMemory, cudaExportMemorySize);
2371 cudaInteropUnavailableLogged =
false;
2372 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
2373 }
catch (
const std::exception &ex) {
2374 if (!cudaInteropUnavailableLogged) {
2375 std::cout << std::format(
"mxvk: CUDA exportable sprite resize unavailable: {}; using CPU/pinned fallback\n", ex.what());
2376 cudaInteropUnavailableLogged =
true;
2381 createDescriptorPool();
2387 if (!ensureCudaInterop() || !transitionCudaImageForWrite()) {
2391 cudaStream_t cudaStream = cuda_stream_handle(stream);
2392 if (!cudaUploadLogged) {
2393 std::cout << std::format(
"mxvk: CUDA interop upload: copying {}x{} RGBA GpuMat to Vulkan image array (pitch={} bytes)\n", rgba.cols, rgba.rows,
static_cast<unsigned long long>(rgba.step));
2394 cudaUploadLogged =
true;
2396 cudaError_t cudaResult = cudaMemcpy2DToArrayAsync(cudaArray, 0, 0, rgba.ptr(), rgba.step,
static_cast<size_t>(rgba.cols) * 4,
static_cast<size_t>(rgba.rows), cudaMemcpyDeviceToDevice, cudaStream);
2397 if (cudaResult != cudaSuccess) {
2398 std::cout << std::format(
"mxvk: CUDA interop texture copy failed: {}\n", cudaGetErrorString(cudaResult));
2402 cudaResult = cudaStreamSynchronize(cudaStream);
2403 if (cudaResult != cudaSuccess) {
2404 std::cout << std::format(
"mxvk: CUDA interop texture sync failed: {}\n", cudaGetErrorString(cudaResult));
2408 cudaImageNeedsShaderBarrier =
true;
2409 return transitionCudaImageForShaderRead();
2412 bool VK_Sprite::updateHistoryTextureCuda(
const cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream) {
2413 if (!historyTextureEnabled || rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols <= 0 || rgba.rows <= 0 ||
static_cast<uint32_t
>(rgba.cols) != historyWidth ||
static_cast<uint32_t
>(rgba.rows) != historyHeight || !ensureCudaHistoryInterop()) {
2417 transitionCudaHistoryLayer(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_MEMORY_WRITE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
2419 cudaMemcpy3DParms copyParameters{};
2420 copyParameters.srcPtr = make_cudaPitchedPtr(
const_cast<unsigned char *
>(rgba.ptr()), rgba.step,
static_cast<size_t>(rgba.cols),
static_cast<size_t>(rgba.rows));
2421 copyParameters.dstArray = cudaHistoryArray;
2422 copyParameters.dstPos = make_cudaPos(0, 0, historyHead);
2423 copyParameters.extent = make_cudaExtent(
static_cast<size_t>(rgba.cols),
static_cast<size_t>(rgba.rows), 1);
2424 copyParameters.kind = cudaMemcpyDeviceToDevice;
2426 cudaStream_t cudaStream = cuda_stream_handle(stream);
2427 cudaError_t cudaResult = cudaMemcpy3DAsync(©Parameters, cudaStream);
2428 if (cudaResult == cudaSuccess) {
2429 cudaResult = cudaStreamSynchronize(cudaStream);
2432 transitionCudaHistoryLayer(VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
2434 if (cudaResult != cudaSuccess) {
2435 std::cout << std::format(
"mxvk: CUDA history interop upload failed: {}\n", cudaGetErrorString(cudaResult));
2438 if (!cudaHistoryUploadLogged) {
2439 std::cout << std::format(
"mxvk: CUDA history interop: copying {}x{} RGBA GpuMat into "
2440 "the Vulkan history array\n",
2443 cudaHistoryUploadLogged =
true;
2445 historyHead = (historyHead + 1) % historyLayers;
2449 bool VK_Sprite::updateTextureCudaHost(
const void *pixels, uint32_t width, uint32_t height, uint32_t pitch) {
2450 if (pixels ==
nullptr || width == 0 || height == 0) {
2453 const uint32_t rowBytes = width * 4U;
2454 if (pitch < rowBytes ||
static_cast<int>(width) != spriteWidth ||
static_cast<int>(height) != spriteHeight) {
2457 if (!ensureCudaInterop() || !transitionCudaImageForWrite()) {
2461 if (!cudaUploadLogged) {
2462 std::cout << std::format(
"mxvk: CUDA interop upload: copying {}x{} host RGBA pixels to Vulkan image array (pitch={} bytes)\n", width, height, pitch);
2463 cudaUploadLogged =
true;
2466 const cudaError_t copyResult = cudaMemcpy2DToArray(cudaArray, 0, 0, pixels, pitch,
static_cast<size_t>(rowBytes),
static_cast<size_t>(height), cudaMemcpyHostToDevice);
2467 if (copyResult != cudaSuccess) {
2468 std::cout << std::format(
"mxvk: CUDA interop host texture copy failed: {}\n", cudaGetErrorString(copyResult));
2472 cudaImageNeedsShaderBarrier =
true;
2473 return transitionCudaImageForShaderRead();
2477 void VK_Sprite::createSpriteTexture(SDL_Surface *surface) {
2478 spriteImageFormat = VK_FORMAT_R8G8B8A8_UNORM;
2479 spriteBytesPerPixel = 4;
2482 createCudaExportableImage(surface->w, surface->h, 1, spriteImage, spriteImageMemory, cudaExportMemorySize);
2483 cudaInteropUnavailableLogged =
false;
2484 }
catch (
const std::exception &ex) {
2485 std::cout << std::format(
"mxvk: CUDA exportable sprite image unavailable: {}; using standard Vulkan image\n", ex.what());
2486 createImage(surface->w, surface->h, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
2489 createImage(surface->w, surface->h, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
2493 if (updateTextureCudaHost(surface->pixels,
static_cast<uint32_t
>(surface->w),
static_cast<uint32_t
>(surface->h),
static_cast<uint32_t
>(surface->pitch))) {
2494 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
2499 VkBuffer stagingBuffer = VK_NULL_HANDLE;
2500 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
2501 VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(surface->w) * surface->h * 4;
2503 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
2506 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
2507 const int rowBytes = surface->w * 4;
2508 if (surface->pitch == rowBytes) {
2509 memcpy(data, surface->pixels, imageSize);
2511 const auto *src =
static_cast<const uint8_t *
>(surface->pixels);
2512 auto *dst =
static_cast<uint8_t *
>(data);
2513 for (
int y = 0; y < surface->h; ++y)
2514 memcpy(dst + y * rowBytes, src + y * surface->pitch, rowBytes);
2516 vkUnmapMemory(device, stagingMemory);
2519 const VkImageLayout uploadOldLayout = (cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED;
2521 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2523 transitionImageLayout(spriteImage, uploadOldLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
2524 copyBufferToImage(stagingBuffer, spriteImage, surface->w, surface->h);
2525 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
2527 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2530 vkDestroyBuffer(device, stagingBuffer,
nullptr);
2531 vkFreeMemory(device, stagingMemory,
nullptr);
2533 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
2536 void VK_Sprite::createSampler() {
2541 VkSamplerCreateInfo samplerInfo{};
2542 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
2543 samplerInfo.magFilter = textureFilter;
2544 samplerInfo.minFilter = textureFilter;
2545 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
2546 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
2547 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
2548 samplerInfo.anisotropyEnable = VK_FALSE;
2549 samplerInfo.borderColor = VK_BORDER_COLOR_INT_TRANSPARENT_BLACK;
2550 samplerInfo.unnormalizedCoordinates = VK_FALSE;
2551 samplerInfo.compareEnable = VK_FALSE;
2552 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
2553 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
2554 samplerInfo.minLod = 0.0f;
2555 samplerInfo.maxLod = 0.0f;
2556 samplerInfo.mipLodBias = 0.0f;
2566 if (!spriteLoaded) {
2567 throw mxvk::Exception(
"VKSprite::drawSprite called before sprite was loaded");
2570 drawQueue.push_back({
static_cast<float>(x),
static_cast<float>(y),
static_cast<float>(
static_cast<int>(spriteWidth * scaleX)),
static_cast<float>(
static_cast<int>(spriteHeight * scaleY)), rotation, shaderParams});
2574 if (!spriteLoaded) {
2575 throw mxvk::Exception(
"VKSprite::drawSpriteRect called before sprite was loaded");
2578 drawQueue.push_back({
static_cast<float>(x),
static_cast<float>(y),
static_cast<float>(w),
static_cast<float>(h), 0.0f, shaderParams});
2584 if (image_view == VK_NULL_HANDLE || width <= 0 || height <= 0) {
2585 throw mxvk::Exception(
"VKSprite::setExternalTexture received an invalid image view");
2587 if (externalTexture && spriteImageView == image_view) {
2588 spriteWidth = width;
2589 spriteHeight = height;
2590 spriteLoaded =
true;
2593 auto cached_descriptor = externalDescriptorSets.find(image_view);
2594 descriptorSet = (cached_descriptor != externalDescriptorSets.end()) ? cached_descriptor->second : VK_NULL_HANDLE;
2595 descriptorSetPool = VK_NULL_HANDLE;
2596 if (!externalTexture) {
2597 destroyTextureDescriptorPools();
2598 }
else if (extendedDescriptorPool != VK_NULL_HANDLE) {
2599 vkDeviceWaitIdle(device);
2600 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
2601 extendedDescriptorPool = VK_NULL_HANDLE;
2602 extendedDescriptorSet = VK_NULL_HANDLE;
2604 if (!externalTexture && spriteImageView != VK_NULL_HANDLE) {
2605 vkDestroyImageView(device, spriteImageView,
nullptr);
2607 if (!externalTexture && spriteImage != VK_NULL_HANDLE) {
2608 vkDestroyImage(device, spriteImage,
nullptr);
2610 if (!externalTexture && spriteImageMemory != VK_NULL_HANDLE) {
2611 vkFreeMemory(device, spriteImageMemory,
nullptr);
2613 spriteImageView = image_view;
2614 spriteImage = VK_NULL_HANDLE;
2615 spriteImageMemory = VK_NULL_HANDLE;
2616 externalTexture =
true;
2617 spriteWidth = width;
2618 spriteHeight = height;
2619 spriteLoaded =
true;
2623 if (!externalTexture && externalDescriptorSets.empty()) {
2626 destroyTextureDescriptorPools();
2631 recordCudaReadyBarrier(cmdBuffer);
2635 void VK_Sprite::renderSprites(VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout, uint32_t screenWidth, uint32_t screenHeight) {
2636 if (drawQueue.empty() || !spriteLoaded || !quadBufferCreated) {
2639 if (descriptorSet == VK_NULL_HANDLE) {
2640 descriptorSet = createDescriptorSet(spriteImageView);
2641 if (externalTexture) {
2642 externalDescriptorSets[spriteImageView] = descriptorSet;
2646 if (instancingEnabled && instancedPipeline != VK_NULL_HANDLE && instanceBuffer != VK_NULL_HANDLE) {
2647 uint32_t instanceCount =
static_cast<uint32_t
>(drawQueue.size());
2649 if (instanceCount > instanceBufferCapacity) {
2650 ensureInstanceBuffer(instanceCount * 2);
2653 SpriteInstanceData *dst =
static_cast<SpriteInstanceData *
>(instanceBufferMapped);
2654 for (uint32_t i = 0; i < instanceCount; ++i) {
2655 const auto &cmd = drawQueue[i];
2656 dst[i].posX = cmd.x;
2657 dst[i].posY = cmd.y;
2658 dst[i].sizeW = cmd.w;
2659 dst[i].sizeH = cmd.h;
2660 dst[i].params[0] = cmd.params.x;
2661 dst[i].params[1] = cmd.params.y;
2662 dst[i].params[2] = cmd.params.z;
2663 dst[i].params[3] = cmd.params.w;
2666 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, instancedPipeline);
2667 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, instancedPipelineLayout, 0, 1, &descriptorSet, 0,
nullptr);
2669 VkBuffer buffers[] = {quadVertexBuffer, instanceBuffer};
2670 VkDeviceSize bufOffsets[] = {0, 0};
2671 vkCmdBindVertexBuffers(cmdBuffer, 0, 2, buffers, bufOffsets);
2672 vkCmdBindIndexBuffer(cmdBuffer, quadIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
2674 float screenSize[2] = {
static_cast<float>(screenWidth),
static_cast<float>(screenHeight)};
2675 vkCmdPushConstants(cmdBuffer, instancedPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(screenSize), screenSize);
2677 vkCmdDrawIndexed(cmdBuffer, 6, instanceCount, 0, 0, 0);
2681 VkPipelineLayout layoutToUse = (customPipeline != VK_NULL_HANDLE) ? customPipelineLayout : pipelineLayout;
2682 if (customPipeline != VK_NULL_HANDLE) {
2683 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, customPipeline);
2687 if (extendedUBOEnabled && customPipeline != VK_NULL_HANDLE) {
2688 updateExtendedUBO();
2689 if (extendedDescriptorSet == VK_NULL_HANDLE) {
2690 createExtendedDescriptorSet();
2692 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layoutToUse, 0, 1, &extendedDescriptorSet, 0,
nullptr);
2694 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layoutToUse, 0, 1, &descriptorSet, 0,
nullptr);
2697 VkBuffer vertexBuffers[] = {quadVertexBuffer};
2698 VkDeviceSize offsets[] = {0};
2699 vkCmdBindVertexBuffers(cmdBuffer, 0, 1, vertexBuffers, offsets);
2700 vkCmdBindIndexBuffer(cmdBuffer, quadIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
2702 for (
const auto &cmd : drawQueue) {
2703 struct SpritePushConstants {
2713 } pc{
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}};
2715 vkCmdPushConstants(cmdBuffer, layoutToUse, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(SpritePushConstants), &pc);
2717 vkCmdDrawIndexed(cmdBuffer, 6, 1, 0, 0, 0);
2723 void VK_Sprite::createDescriptorPool() {
2724 VkDescriptorPoolSize poolSize{};
2725 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
2726 poolSize.descriptorCount = nextDescriptorPoolSets;
2728 VkDescriptorPoolCreateInfo poolInfo{};
2729 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
2730 poolInfo.poolSizeCount = 1;
2731 poolInfo.pPoolSizes = &poolSize;
2732 poolInfo.maxSets = nextDescriptorPoolSets;
2733 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
2735 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo,
nullptr, &descriptorPool));
2736 descriptorPools.push_back(descriptorPool);
2737 if (nextDescriptorPoolSets <= (std::numeric_limits<uint32_t>::max() / 2U)) {
2738 nextDescriptorPoolSets *= 2U;
2742 void VK_Sprite::destroyDescriptorPools() {
2743 for (VkDescriptorPool pool : descriptorPools) {
2744 if (pool != VK_NULL_HANDLE) {
2745 vkDestroyDescriptorPool(device, pool,
nullptr);
2748 descriptorPools.clear();
2749 descriptorPool = VK_NULL_HANDLE;
2750 descriptorSetPool = VK_NULL_HANDLE;
2751 descriptorSet = VK_NULL_HANDLE;
2752 externalDescriptorSets.clear();
2753 nextDescriptorPoolSets = 16;
2756 void VK_Sprite::destroyTextureDescriptorPools() {
2757 if (!descriptorPools.empty() || extendedDescriptorPool != VK_NULL_HANDLE) {
2758 vkDeviceWaitIdle(device);
2760 destroyDescriptorPools();
2761 if (extendedDescriptorPool != VK_NULL_HANDLE) {
2762 vkDestroyDescriptorPool(device, extendedDescriptorPool,
nullptr);
2763 extendedDescriptorPool = VK_NULL_HANDLE;
2764 extendedDescriptorSet = VK_NULL_HANDLE;
2768 VkDescriptorSet VK_Sprite::createDescriptorSet(VkImageView imageView) {
2769 if (descriptorSetLayout == VK_NULL_HANDLE) {
2770 throw mxvk::Exception(
"VKSprite::createDescriptorSet called before setDescriptorSetLayout");
2773 if (descriptorPool == VK_NULL_HANDLE) {
2774 createDescriptorPool();
2777 VkDescriptorSetAllocateInfo allocInfo{};
2778 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
2779 allocInfo.descriptorPool = descriptorPool;
2780 allocInfo.descriptorSetCount = 1;
2781 allocInfo.pSetLayouts = &descriptorSetLayout;
2783 VkDescriptorSet descSet = VK_NULL_HANDLE;
2784 VkResult allocateResult = vkAllocateDescriptorSets(device, &allocInfo, &descSet);
2785 if (allocateResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocateResult == VK_ERROR_FRAGMENTED_POOL) {
2786 createDescriptorPool();
2787 allocInfo.descriptorPool = descriptorPool;
2788 allocateResult = vkAllocateDescriptorSets(device, &allocInfo, &descSet);
2790 if (allocateResult != VK_SUCCESS) {
2791 throw mxvk::Exception(std::format(
"Fatal : VkResult is \"{}\" in {} at line {}",
static_cast<int>(allocateResult), __FILE__, __LINE__));
2793 descriptorSetPool = allocInfo.descriptorPool;
2795 VkDescriptorImageInfo imageInfo{};
2796 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2797 imageInfo.imageView = imageView;
2800 VkWriteDescriptorSet descriptorWrite{};
2801 descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
2802 descriptorWrite.dstSet = descSet;
2803 descriptorWrite.dstBinding = 0;
2804 descriptorWrite.dstArrayElement = 0;
2805 descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
2806 descriptorWrite.descriptorCount = 1;
2807 descriptorWrite.pImageInfo = &imageInfo;
2809 vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0,
nullptr);
2814 void VK_Sprite::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory) {
2815 VkBuffer newBuffer = VK_NULL_HANDLE;
2816 VkDeviceMemory newMemory = VK_NULL_HANDLE;
2818 VkBufferCreateInfo bufferInfo{};
2819 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
2820 bufferInfo.size = size;
2821 bufferInfo.usage = usage;
2822 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2825 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo,
nullptr, &newBuffer));
2827 VkMemoryRequirements memRequirements;
2828 vkGetBufferMemoryRequirements(device, newBuffer, &memRequirements);
2830 VkMemoryAllocateInfo allocInfo{};
2831 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
2832 allocInfo.allocationSize = memRequirements.size;
2833 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
2834 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
2837 if (newBuffer != VK_NULL_HANDLE) {
2838 vkDestroyBuffer(device, newBuffer,
nullptr);
2840 if (newMemory != VK_NULL_HANDLE) {
2841 vkFreeMemory(device, newMemory,
nullptr);
2846 if (buffer != VK_NULL_HANDLE) {
2847 vkDestroyBuffer(device, buffer,
nullptr);
2849 if (bufferMemory != VK_NULL_HANDLE) {
2850 vkFreeMemory(device, bufferMemory,
nullptr);
2853 bufferMemory = newMemory;
2856 uint32_t VK_Sprite::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
2857 VkPhysicalDeviceMemoryProperties memProperties;
2858 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
2860 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
2861 if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
2866 throw mxvk::Exception(
"Failed to find suitable memory type!");
2869 void VK_Sprite::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t baseArrayLayer, uint32_t layerCount) {
2870 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2872 VkImageMemoryBarrier barrier{};
2873 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2874 barrier.oldLayout = oldLayout;
2875 barrier.newLayout = newLayout;
2876 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2877 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2878 barrier.image = image;
2879 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2880 barrier.subresourceRange.baseMipLevel = 0;
2881 barrier.subresourceRange.levelCount = 1;
2882 barrier.subresourceRange.baseArrayLayer = baseArrayLayer;
2883 barrier.subresourceRange.layerCount = layerCount;
2885 VkPipelineStageFlags sourceStage;
2886 VkPipelineStageFlags destinationStage;
2888 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
2889 barrier.srcAccessMask = 0;
2890 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2891 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2892 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2893 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
2894 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2895 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
2896 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2897 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
2898 }
else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
2899 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
2900 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2901 sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
2902 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2903 }
else if (oldLayout == VK_IMAGE_LAYOUT_GENERAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
2904 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
2905 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2906 sourceStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
2907 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2909 throw std::invalid_argument(
"unsupported layout transition!");
2912 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
2913 endSingleTimeCommands(commandBuffer);
2916 void VK_Sprite::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height, uint32_t baseArrayLayer, uint32_t layerCount) {
2917 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2919 VkBufferImageCopy region{};
2920 region.bufferOffset = 0;
2921 region.bufferRowLength = 0;
2922 region.bufferImageHeight = 0;
2923 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2924 region.imageSubresource.mipLevel = 0;
2925 region.imageSubresource.baseArrayLayer = baseArrayLayer;
2926 region.imageSubresource.layerCount = layerCount;
2927 region.imageOffset = {0, 0, 0};
2928 region.imageExtent = {width, height, 1};
2930 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
2932 endSingleTimeCommands(commandBuffer);
2935 VkCommandBuffer VK_Sprite::beginSingleTimeCommands() {
2936 VkCommandBufferAllocateInfo allocInfo{};
2937 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
2938 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
2939 allocInfo.commandPool = commandPool;
2940 allocInfo.commandBufferCount = 1;
2942 VkCommandBuffer commandBuffer;
2943 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
2945 VkCommandBufferBeginInfo beginInfo{};
2946 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
2947 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
2951 return commandBuffer;
2954 void VK_Sprite::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
2957 VkSubmitInfo submitInfo{};
2958 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2959 submitInfo.commandBufferCount = 1;
2960 submitInfo.pCommandBuffers = &commandBuffer;
2962 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE));
2965 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
2968 void VK_Sprite::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &imageMemory, uint32_t arrayLayers, VkImageType imageType) {
2969 VkImage newImage = VK_NULL_HANDLE;
2970 VkDeviceMemory newMemory = VK_NULL_HANDLE;
2972 VkImageCreateInfo imageInfo{};
2973 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2974 imageInfo.imageType = imageType;
2975 imageInfo.extent.width = width;
2976 imageInfo.extent.height = height;
2977 imageInfo.extent.depth = 1;
2978 imageInfo.mipLevels = 1;
2979 imageInfo.arrayLayers = arrayLayers;
2980 imageInfo.format = format;
2981 imageInfo.tiling = tiling;
2982 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2983 imageInfo.usage = usage;
2984 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2985 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2988 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo,
nullptr, &newImage));
2990 VkMemoryRequirements memRequirements;
2991 vkGetImageMemoryRequirements(device, newImage, &memRequirements);
2993 VkMemoryAllocateInfo allocInfo{};
2994 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
2995 allocInfo.allocationSize = memRequirements.size;
2996 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
2997 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo,
nullptr, &newMemory));
3000 if (newImage != VK_NULL_HANDLE) {
3001 vkDestroyImage(device, newImage,
nullptr);
3003 if (newMemory != VK_NULL_HANDLE) {
3004 vkFreeMemory(device, newMemory,
nullptr);
3009 if (image != VK_NULL_HANDLE) {
3010 vkDestroyImage(device, image,
nullptr);
3012 if (imageMemory != VK_NULL_HANDLE) {
3013 vkFreeMemory(device, imageMemory,
nullptr);
3016 imageMemory = newMemory;
3019 VkImageView VK_Sprite::createImageView(VkImage image, VkFormat format, VkImageViewType viewType, uint32_t layerCount) {
3020 VkImageViewCreateInfo viewInfo{};
3021 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
3022 viewInfo.image = image;
3023 viewInfo.viewType = viewType;
3024 viewInfo.format = format;
3025 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3026 viewInfo.subresourceRange.baseMipLevel = 0;
3027 viewInfo.subresourceRange.levelCount = 1;
3028 viewInfo.subresourceRange.baseArrayLayer = 0;
3029 viewInfo.subresourceRange.layerCount = layerCount;
3031 VkImageView imageView;
3032 VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo,
nullptr, &imageView));
3036 SDL_Surface *VK_Sprite::convertToRGBA(SDL_Surface *surface) {
3038 SDL_Surface *converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
3042 std::vector<char> VK_Sprite::readShaderFile(
const std::string &filename) {
3043 std::vector<std::filesystem::path> candidates{};
3044 const std::filesystem::path requested(filename);
3046 if (requested.is_absolute() || requested.has_parent_path()) {
3047 candidates.push_back(requested);
3049 if (
const char *basePath = SDL_GetBasePath(); basePath !=
nullptr) {
3050 const std::filesystem::path executableDir(basePath);
3051 candidates.push_back(executableDir /
"data" / requested);
3052 candidates.push_back(executableDir / requested);
3054 candidates.push_back(std::filesystem::path(
"data") / requested);
3055 candidates.push_back(requested);
3059 for (
const std::filesystem::path &candidate : candidates) {
3060 file.open(candidate, std::ios::ate | std::ios::binary);
3061 if (file.is_open()) {
3067 if (!file.is_open()) {
3068 throw mxvk::Exception(
"Failed to open shader file: " + filename);
3071 size_t fileSize =
static_cast<size_t>(file.tellg());
3072 std::vector<char> buffer(fileSize);
3074 file.read(buffer.data(), fileSize);
void createEmptySpriteRgba16(int width, int height, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a blank RGBA16 UNORM sprite texture.
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 updateTextureRgba16(const uint16_t *pixels, int width, int height, int pitch=0)
Replace an RGBA16 UNORM sprite from native-endian uint16 data.
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 updateHistoryTextureRgba16(const uint16_t *pixels, int width, int height, int pitch=0)
Upload normalized RGBA16 pixels into an RGBA16F history layer.
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 enableHistoryTextureRgba16Float(uint32_t width, uint32_t height, uint32_t layers)
Allocate a shader-readable RGBA16F history texture array.
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)
uint16_t float_to_half(float value) noexcept
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.