MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_sprite.cpp
Go to the documentation of this file.
1/**
2 * @file mxvk_sprite.cpp
3 * @brief Implementation of mxvk::VK_Sprite Vulkan 2-D sprite renderer.
4 */
7#include "mxvk/mxvk_png.hpp"
9#include <algorithm>
10#include <bit>
11#include <filesystem>
12#include <limits>
13#ifdef MXVK_CUDA
14#include <unistd.h>
15#endif
16
17namespace mxvk {
18 namespace {
19 [[nodiscard]] uint16_t float_to_half(float value) noexcept {
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;
24 if (exponent < -24) {
25 return static_cast<uint16_t>(sign);
26 }
27 if (exponent < -14) {
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);
32 }
33 if (exponent > 15) {
34 return static_cast<uint16_t>(sign | 0x7C00U);
35 }
36 uint32_t half_exponent = static_cast<uint32_t>(exponent + 15);
37 mantissa += 0x1000U;
38 if ((mantissa & 0x800000U) != 0U) {
39 mantissa = 0U;
40 ++half_exponent;
41 }
42 if (half_exponent >= 31U) {
43 return static_cast<uint16_t>(sign | 0x7C00U);
44 }
45 return static_cast<uint16_t>(sign | (half_exponent << 10U) | (mantissa >> 13U));
46 }
47 } // namespace
48
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"; }
50
51 void VK_Sprite::releaseUploadResources() { destroyStagingResources(); }
52
53 void VK_Sprite::setCommandPool(VkCommandPool pool) {
54 if (pool == commandPool) {
55 return;
56 }
57
58 // uploadCmdBuffer is allocated from commandPool, so it must be released
59 // before switching to another pool.
60 destroyStagingResources();
61 commandPool = pool;
62 }
63
64 void VK_Sprite::setTextureFilter(VkFilter filter) {
65 if (filter != VK_FILTER_NEAREST && filter != VK_FILTER_LINEAR) {
66 throw mxvk::Exception("VKSprite::setTextureFilter supports only nearest or linear filtering");
67 }
68 if (textureFilter == filter) {
69 return;
70 }
71
72 textureFilter = filter;
73 if (spriteSampler != VK_NULL_HANDLE) {
74 destroyTextureDescriptorPools();
75 vkDestroySampler(device, spriteSampler, nullptr);
76 spriteSampler = VK_NULL_HANDLE;
77 createSampler();
78 }
79
80 std::cout << "mxvk: Sprite texture filter set to " << (textureFilter == VK_FILTER_NEAREST ? "nearest\n" : "linear\n");
81 }
82
84 vkDeviceWaitIdle(device);
85 drawQueue.clear();
86
87 destroyStagingResources();
88#ifdef MXVK_CUDA
89 destroyCudaInterop();
90#endif
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);
95 }
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);
100 }
101
102 destroyTextureDescriptorPools();
103
104 if (spriteSampler != VK_NULL_HANDLE) {
105 std::cout << "vk: destroying sprite sampler\n";
106 vkDestroySampler(device, spriteSampler, nullptr);
107 }
108
109 if (!externalTexture && spriteImageView != VK_NULL_HANDLE) {
110 std::cout << "vk: destroying sprite image view\n";
111 vkDestroyImageView(device, spriteImageView, nullptr);
112 }
113
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);
119 }
120
121 if (fragmentShaderModule != VK_NULL_HANDLE) {
122 vkDestroyShaderModule(device, fragmentShaderModule, nullptr);
123 }
124
125 if (customPipeline != VK_NULL_HANDLE) {
126 std::cout << "vk: destroying sprite custom pipeline\n";
127 vkDestroyPipeline(device, customPipeline, nullptr);
128 }
129
130 if (customPipelineLayout != VK_NULL_HANDLE) {
131 std::cout << "vk: destroying sprite custom pipeline layout\n";
132 vkDestroyPipelineLayout(device, customPipelineLayout, nullptr);
133 }
134
135 destroyComputePipeline();
136 if (computeShaderModule != VK_NULL_HANDLE) {
137 vkDestroyShaderModule(device, computeShaderModule, nullptr);
138 computeShaderModule = VK_NULL_HANDLE;
139 }
140
141 destroyExtendedUBO();
142 destroyHistoryTexture();
143 destroySpectrumTexture();
144 destroySpectrumHistoryTexture();
145 destroyInstanceResources();
146 }
147
148 void VK_Sprite::destroySpriteResources() {
149 destroyStagingResources();
150#ifdef MXVK_CUDA
151 destroyCudaInterop();
152#endif
153
154 destroyTextureDescriptorPools();
155
156 if (spriteSampler != VK_NULL_HANDLE) {
157 std::cout << "vk: destroying sprite sampler\n";
158 vkDestroySampler(device, spriteSampler, nullptr);
159 spriteSampler = VK_NULL_HANDLE;
160 }
161
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;
166 }
167
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;
172 }
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;
177 }
178 externalTexture = false;
179
180 if (fragmentShaderModule != VK_NULL_HANDLE) {
181 vkDestroyShaderModule(device, fragmentShaderModule, nullptr);
182 fragmentShaderModule = VK_NULL_HANDLE;
183 }
184
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;
189 }
190
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;
195 }
196
197 hasCustomShader = false;
198 spriteLoaded = false;
199 }
200
202 if (extendedUBOEnabled)
203 return;
204 extendedUBOEnabled = true;
205 createExtendedUBO();
206 createExtendedDescriptorSetLayout();
208 }
209
210 void VK_Sprite::setMouseState(float mx, float my, float pressed, float reserved) { extendedUBOData.mouse = glm::vec4(mx, my, pressed, reserved); }
211
212 void VK_Sprite::setUniform0(float x, float y, float z, float w) { extendedUBOData.u0 = glm::vec4(x, y, z, w); }
213
214 void VK_Sprite::setUniform1(float x, float y, float z, float w) { extendedUBOData.u1 = glm::vec4(x, y, z, w); }
215
216 void VK_Sprite::setUniform2(float x, float y, float z, float w) { extendedUBOData.u2 = glm::vec4(x, y, z, w); }
217
218 void VK_Sprite::setUniform3(float x, float y, float z, float w) { extendedUBOData.u3 = glm::vec4(x, y, z, w); }
219
220 void VK_Sprite::setAudioBands(float low, float mid, float high, float reserved) { extendedUBOData.audio_bands = glm::vec4(low, mid, high, reserved); }
221
222 void VK_Sprite::setCustomUniforms(const std::vector<float> &values) {
223 if (values.size() > MAX_CUSTOM_UNIFORMS) {
224 throw mxvk::Exception(std::format("VKSprite::setCustomUniforms supports at most {} values", MAX_CUSTOM_UNIFORMS));
225 }
226
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];
230 }
231 }
232
233 void VK_Sprite::enableHistoryTexture(uint32_t width, uint32_t height, uint32_t layers) { enableHistoryTextureWithFormat(width, height, layers, VK_FORMAT_R8G8B8A8_UNORM); }
234
235 void VK_Sprite::enableHistoryTextureRgba16Float(uint32_t width, uint32_t height, uint32_t layers) { enableHistoryTextureWithFormat(width, height, layers, VK_FORMAT_R16G16B16A16_SFLOAT); }
236
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");
240 }
241
242 if (historyTextureEnabled && historyWidth == width && historyHeight == height && historyLayers == layers && historyImageFormat == format) {
243 return;
244 }
245
246 if (!extendedUBOEnabled) {
248 }
249
250 vkDeviceWaitIdle(device);
251 destroyHistoryTexture();
252
253 historyWidth = width;
254 historyHeight = height;
255 historyLayers = layers;
256 historyHead = 0;
257 historyImageFormat = format;
258#ifdef MXVK_CUDA
259 if (format == VK_FORMAT_R8G8B8A8_UNORM) {
260 try {
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",
266 exception.what());
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);
268 }
269 } else {
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);
271 }
272#else
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);
274#endif
275
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);
282
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);
287
288 transitionImageLayout(historyImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, layers);
289
290 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
291 std::vector<VkBufferImageCopy> regions(layers);
292 for (uint32_t layer = 0; layer < layers; ++layer) {
293 VkBufferImageCopy &region = 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};
300 }
301 vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, historyImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast<uint32_t>(regions.size()), regions.data());
302 endSingleTimeCommands(commandBuffer);
303
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);
307
308 historyImageView = createImageView(historyImage, format, VK_IMAGE_VIEW_TYPE_2D_ARRAY, layers);
309 historyTextureEnabled = true;
310 historyTextureShared = false;
311 recreateExtendedDescriptorLayout();
312 }
313
315 if (source.device != device) {
316 throw mxvk::Exception("VKSprite::shareHistoryTexture requires sprites on the same Vulkan device");
317 }
318 if (!source.historyTextureEnabled || source.historyImageView == VK_NULL_HANDLE || source.historyLayers == 0) {
319 throw mxvk::Exception("VKSprite::shareHistoryTexture source has no enabled history texture");
320 }
321 if (&source == this) {
322 throw mxvk::Exception("VKSprite::shareHistoryTexture cannot share a sprite with itself");
323 }
324
325 if (!extendedUBOEnabled) {
327 }
328
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();
340 }
341
342 void VK_Sprite::updateHistoryTexture(const void *pixels, int width, int height, int pitch) {
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");
347 }
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);
355 }
356 }
357 uploadHistoryTextureBytes(converted.data(), width, height, width * 8, 8);
358 return;
359 }
360 uploadHistoryTextureBytes(pixels, width, height, pitch, 4);
361 }
362
363 void VK_Sprite::updateHistoryTextureRgba16(const uint16_t *pixels, int width, int height, int pitch) {
364 if (historyImageFormat != VK_FORMAT_R16G16B16A16_SFLOAT) {
365 throw mxvk::Exception("VKSprite::updateHistoryTextureRgba16 requires RGBA16F history");
366 }
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");
370 }
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);
378 }
379 }
380 uploadHistoryTextureBytes(converted.data(), width, height, width * 8, 8);
381 }
382
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");
386 }
387 if (pixels == nullptr) {
388 throw mxvk::Exception("VKSprite::updateHistoryTexture called with null pixel data");
389 }
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");
392 }
393
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");
398 }
399
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));
403 VK_CHECK_RESULT(vkResetFences(device, 1, &uploadFence));
404
405 if (sourcePitch == row_size) {
406 memcpy(persistentStagingMapped, pixels, static_cast<std::size_t>(imageSize));
407 } else {
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));
412 }
413 }
414
415 VK_CHECK_RESULT(vkResetCommandBuffer(uploadCmdBuffer, 0));
416 VkCommandBufferBeginInfo beginInfo{};
417 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
418 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
419 VK_CHECK_RESULT(vkBeginCommandBuffer(uploadCmdBuffer, &beginInfo));
420
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);
436
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, &region);
444
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);
450
451 VK_CHECK_RESULT(vkEndCommandBuffer(uploadCmdBuffer));
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));
458
459 historyHead = (historyHead + 1) % historyLayers;
460 }
461
463 if (bins == 0) {
464 throw mxvk::Exception("VKSprite::enableSpectrumTexture requires a positive bin count");
465 }
466 if (spectrumTextureEnabled && spectrumBins == bins) {
467 return;
468 }
469 if (!extendedUBOEnabled) {
471 }
472
473 vkDeviceWaitIdle(device);
474 destroySpectrumTexture();
475
476 spectrumBins = bins;
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);
478
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);
483
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);
488
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);
492
493 vkDestroyBuffer(device, stagingBuffer, nullptr);
494 vkFreeMemory(device, stagingMemory, nullptr);
495
496 spectrumImageView = createImageView(spectrumImage, VK_FORMAT_R32_SFLOAT, VK_IMAGE_VIEW_TYPE_1D);
497 spectrumTextureEnabled = true;
498 recreateExtendedDescriptorLayout();
499 }
500
501 void VK_Sprite::updateSpectrumTexture(const float *magnitudes, uint32_t bins) {
502 if (!spectrumTextureEnabled || spectrumImage == VK_NULL_HANDLE) {
503 throw mxvk::Exception("VKSprite::updateSpectrumTexture called before enableSpectrumTexture");
504 }
505 if (magnitudes == nullptr) {
506 throw mxvk::Exception("VKSprite::updateSpectrumTexture called with null data");
507 }
508 if (bins != spectrumBins) {
509 throw mxvk::Exception("VKSprite::updateSpectrumTexture bin count does not match the spectrum texture");
510 }
511
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));
515 VK_CHECK_RESULT(vkResetFences(device, 1, &uploadFence));
516 memcpy(persistentStagingMapped, magnitudes, static_cast<std::size_t>(imageSize));
517
518 VK_CHECK_RESULT(vkResetCommandBuffer(uploadCmdBuffer, 0));
519 VkCommandBufferBeginInfo beginInfo{};
520 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
521 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
522 VK_CHECK_RESULT(vkBeginCommandBuffer(uploadCmdBuffer, &beginInfo));
523
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);
539
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, &region);
547
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);
553
554 VK_CHECK_RESULT(vkEndCommandBuffer(uploadCmdBuffer));
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));
561 }
562
563 uint32_t VK_Sprite::enableSpectrumHistoryTexture(uint32_t bins, uint32_t layers) {
564 if (bins == 0 || layers == 0) {
565 throw mxvk::Exception("VKSprite::enableSpectrumHistoryTexture requires positive bin and layer counts");
566 }
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");
572 }
573 if (spectrumHistoryTextureEnabled && spectrumHistoryBins == bins && spectrumHistoryLayers == allocatedLayers) {
574 return spectrumHistoryLayers;
575 }
576 if (!extendedUBOEnabled) {
578 }
579
580 vkDeviceWaitIdle(device);
581 destroySpectrumHistoryTexture();
582
583 if (allocatedLayers != layers) {
584 std::cerr << "vk: spectrum history clamped to device array-layer limit " << allocatedLayers << " (was " << layers << ")\n";
585 }
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);
591
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);
593
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);
598
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);
603
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);
607
608 vkDestroyBuffer(device, stagingBuffer, nullptr);
609 vkFreeMemory(device, stagingMemory, nullptr);
610
611 spectrumHistoryImageView = createImageView(spectrumHistoryImage, VK_FORMAT_R32_SFLOAT, VK_IMAGE_VIEW_TYPE_1D_ARRAY, allocatedLayers);
612 spectrumHistoryTextureEnabled = true;
613 recreateExtendedDescriptorLayout();
614 return allocatedLayers;
615 }
616
617 void VK_Sprite::updateSpectrumHistoryTexture(const float *magnitudes, uint32_t bins) {
618 if (!spectrumHistoryTextureEnabled || spectrumHistoryImage == VK_NULL_HANDLE) {
619 throw mxvk::Exception("VKSprite::updateSpectrumHistoryTexture called before enableSpectrumHistoryTexture");
620 }
621 if (magnitudes == nullptr) {
622 throw mxvk::Exception("VKSprite::updateSpectrumHistoryTexture called with null data");
623 }
624 if (bins != spectrumHistoryBins) {
625 throw mxvk::Exception("VKSprite::updateSpectrumHistoryTexture bin count does not match the history texture");
626 }
627
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));
631 VK_CHECK_RESULT(vkResetFences(device, 1, &uploadFence));
632 memcpy(persistentStagingMapped, magnitudes, static_cast<std::size_t>(imageSize));
633
634 VK_CHECK_RESULT(vkResetCommandBuffer(uploadCmdBuffer, 0));
635 VkCommandBufferBeginInfo beginInfo{};
636 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
637 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
638 VK_CHECK_RESULT(vkBeginCommandBuffer(uploadCmdBuffer, &beginInfo));
639
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);
655
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, &region);
663
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);
669
670 VK_CHECK_RESULT(vkEndCommandBuffer(uploadCmdBuffer));
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));
677
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);
681 }
682
683 void VK_Sprite::createExtendedUBO() {
684 if (extendedUBOBuffer != VK_NULL_HANDLE)
685 return;
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));
689 }
690
691 void VK_Sprite::updateExtendedUBO() {
692 if (!extendedUBOEnabled || !extendedUBOMapped)
693 return;
694 memcpy(extendedUBOMapped, &extendedUBOData, sizeof(SpriteExtendedUBO));
695 }
696
697 void VK_Sprite::createExtendedDescriptorSetLayout() {
698 if (extendedDescriptorSetLayout != VK_NULL_HANDLE)
699 return;
700
701 std::vector<VkDescriptorSetLayoutBinding> bindings(2);
702 // binding 0: combined image sampler (same as original)
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;
708 // binding 1: uniform buffer for extended data
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);
721 }
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);
729 }
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);
737 }
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);
745 }
746
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();
751
752 VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &extendedDescriptorSetLayout));
753 ownExtendedDescriptorSetLayout = true;
754 }
755
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))
758 return;
759
760 if (extendedDescriptorPool != VK_NULL_HANDLE) {
761 vkDeviceWaitIdle(device);
762 vkDestroyDescriptorPool(device, extendedDescriptorPool, nullptr);
763 extendedDescriptorPool = VK_NULL_HANDLE;
764 extendedDescriptorSet = VK_NULL_HANDLE;
765 }
766
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;
774
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;
780
781 VK_CHECK_RESULT(vkCreateDescriptorPool(device, &poolInfo, nullptr, &extendedDescriptorPool));
782
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;
788
789 VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &extendedDescriptorSet));
790
791 VkDescriptorImageInfo imageInfo{};
792 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
793 imageInfo.imageView = spriteImageView;
794 imageInfo.sampler = spriteSampler;
795
796 VkDescriptorBufferInfo bufferInfo{};
797 bufferInfo.buffer = extendedUBOBuffer;
798 bufferInfo.offset = 0;
799 bufferInfo.range = sizeof(SpriteExtendedUBO);
800
801 VkDescriptorImageInfo historyImageInfo{};
802 historyImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
803 historyImageInfo.imageView = historyImageView;
804 historyImageInfo.sampler = spriteSampler;
805
806 VkDescriptorImageInfo spectrumImageInfo{};
807 spectrumImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
808 spectrumImageInfo.imageView = spectrumImageView;
809 spectrumImageInfo.sampler = spriteSampler;
810
811 VkDescriptorImageInfo spectrumHistoryImageInfo{};
812 spectrumHistoryImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
813 spectrumHistoryImageInfo.imageView = spectrumHistoryImageView;
814 spectrumHistoryImageInfo.sampler = spriteSampler;
815
816 VkDescriptorImageInfo outputImageInfo{};
817 outputImageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
818 outputImageInfo.imageView = computeOutputImageView;
819
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;
828
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;
836
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);
846 }
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);
856 }
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);
866 }
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);
876 }
877
878 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
879 }
880
881 void VK_Sprite::recreateExtendedDescriptorLayout() {
882 vkDeviceWaitIdle(device);
883
884 if (customPipeline != VK_NULL_HANDLE) {
885 vkDestroyPipeline(device, customPipeline, nullptr);
886 customPipeline = VK_NULL_HANDLE;
887 }
888 if (customPipelineLayout != VK_NULL_HANDLE) {
889 vkDestroyPipelineLayout(device, customPipelineLayout, nullptr);
890 customPipelineLayout = VK_NULL_HANDLE;
891 }
892 destroyComputePipeline();
893 if (extendedDescriptorPool != VK_NULL_HANDLE) {
894 vkDestroyDescriptorPool(device, extendedDescriptorPool, nullptr);
895 extendedDescriptorPool = VK_NULL_HANDLE;
896 extendedDescriptorSet = VK_NULL_HANDLE;
897 }
898 if (ownExtendedDescriptorSetLayout && extendedDescriptorSetLayout != VK_NULL_HANDLE) {
899 vkDestroyDescriptorSetLayout(device, extendedDescriptorSetLayout, nullptr);
900 extendedDescriptorSetLayout = VK_NULL_HANDLE;
901 ownExtendedDescriptorSetLayout = false;
902 }
903
904 createExtendedDescriptorSetLayout();
906 createComputePipeline();
907 }
908
909 void VK_Sprite::destroyHistoryTexture() {
910#ifdef MXVK_CUDA
911 if (!historyTextureShared) {
912 destroyCudaHistoryInterop();
913 }
914#endif
915 if (!historyTextureShared && historyImageView != VK_NULL_HANDLE) {
916 vkDestroyImageView(device, historyImageView, nullptr);
917 }
918 historyImageView = VK_NULL_HANDLE;
919 if (historyImage != VK_NULL_HANDLE) {
920 vkDestroyImage(device, historyImage, nullptr);
921 historyImage = VK_NULL_HANDLE;
922 }
923 if (historyImageMemory != VK_NULL_HANDLE) {
924 vkFreeMemory(device, historyImageMemory, nullptr);
925 historyImageMemory = VK_NULL_HANDLE;
926 }
927 historyTextureEnabled = false;
928 historyTextureShared = false;
929 historyWidth = 0;
930 historyHeight = 0;
931 historyLayers = 0;
932 historyHead = 0;
933 historyImageFormat = VK_FORMAT_R8G8B8A8_UNORM;
934 }
935
936 void VK_Sprite::destroySpectrumTexture() {
937 if (spectrumImageView != VK_NULL_HANDLE) {
938 vkDestroyImageView(device, spectrumImageView, nullptr);
939 spectrumImageView = VK_NULL_HANDLE;
940 }
941 if (spectrumImage != VK_NULL_HANDLE) {
942 vkDestroyImage(device, spectrumImage, nullptr);
943 spectrumImage = VK_NULL_HANDLE;
944 }
945 if (spectrumImageMemory != VK_NULL_HANDLE) {
946 vkFreeMemory(device, spectrumImageMemory, nullptr);
947 spectrumImageMemory = VK_NULL_HANDLE;
948 }
949 spectrumTextureEnabled = false;
950 spectrumBins = 0;
951 }
952
953 void VK_Sprite::destroySpectrumHistoryTexture() {
954 if (spectrumHistoryImageView != VK_NULL_HANDLE) {
955 vkDestroyImageView(device, spectrumHistoryImageView, nullptr);
956 spectrumHistoryImageView = VK_NULL_HANDLE;
957 }
958 if (spectrumHistoryImage != VK_NULL_HANDLE) {
959 vkDestroyImage(device, spectrumHistoryImage, nullptr);
960 spectrumHistoryImage = VK_NULL_HANDLE;
961 }
962 if (spectrumHistoryImageMemory != VK_NULL_HANDLE) {
963 vkFreeMemory(device, spectrumHistoryImageMemory, nullptr);
964 spectrumHistoryImageMemory = VK_NULL_HANDLE;
965 }
966 spectrumHistoryTextureEnabled = false;
967 spectrumHistoryBins = 0;
968 spectrumHistoryLayers = 0;
969 spectrumHistoryHead = 0;
970 spectrumHistoryWriteIndex = 0;
971 extendedUBOData.audio_history = glm::vec4(0.0f);
972 }
973
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;
980 }
981 if (ownExtendedDescriptorSetLayout && extendedDescriptorSetLayout != VK_NULL_HANDLE) {
982 vkDestroyDescriptorSetLayout(device, extendedDescriptorSetLayout, nullptr);
983 extendedDescriptorSetLayout = VK_NULL_HANDLE;
984 ownExtendedDescriptorSetLayout = false;
985 }
986 if (extendedUBOBuffer != VK_NULL_HANDLE) {
987 if (extendedUBOMapped) {
988 vkUnmapMemory(device, extendedUBOMemory);
989 extendedUBOMapped = nullptr;
990 }
991 vkDestroyBuffer(device, extendedUBOBuffer, nullptr);
992 vkFreeMemory(device, extendedUBOMemory, nullptr);
993 extendedUBOBuffer = VK_NULL_HANDLE;
994 extendedUBOMemory = VK_NULL_HANDLE;
995 }
996 extendedUBOEnabled = false;
997 }
998
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;
1003 }
1004 return std::max(allocationSize, requiredSize);
1005 }
1006
1007 void VK_Sprite::createStagingResources(VkDeviceSize size) {
1008 const VkDeviceSize allocationSize = stagingAllocationSize(size);
1009 if (stagingResourcesCreated && persistentStagingSize >= size) {
1010 return;
1011 }
1012 destroyStagingResources();
1013
1014 createBuffer(allocationSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, persistentStagingBuffer, persistentStagingMemory);
1015
1016 try {
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;
1030 } catch (...) {
1031 if (uploadCmdBuffer != VK_NULL_HANDLE) {
1032 vkFreeCommandBuffers(device, commandPool, 1, &uploadCmdBuffer);
1033 uploadCmdBuffer = VK_NULL_HANDLE;
1034 }
1035 if (persistentStagingMapped) {
1036 vkUnmapMemory(device, persistentStagingMemory);
1037 persistentStagingMapped = nullptr;
1038 }
1039 if (persistentStagingBuffer != VK_NULL_HANDLE) {
1040 vkDestroyBuffer(device, persistentStagingBuffer, nullptr);
1041 persistentStagingBuffer = VK_NULL_HANDLE;
1042 }
1043 if (persistentStagingMemory != VK_NULL_HANDLE) {
1044 vkFreeMemory(device, persistentStagingMemory, nullptr);
1045 persistentStagingMemory = VK_NULL_HANDLE;
1046 }
1047 persistentStagingSize = 0;
1048 throw;
1049 }
1050 }
1051
1052 void VK_Sprite::destroyStagingResources() {
1053 if (!stagingResourcesCreated)
1054 return;
1055
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;
1060 }
1061 if (uploadCmdBuffer != VK_NULL_HANDLE) {
1062 vkFreeCommandBuffers(device, commandPool, 1, &uploadCmdBuffer);
1063 uploadCmdBuffer = VK_NULL_HANDLE;
1064 }
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;
1073 }
1074 stagingResourcesCreated = false;
1075 }
1076
1077 void VK_Sprite::destroyInstanceResources() {
1078 if (instanceBuffer != VK_NULL_HANDLE) {
1079 if (instanceBufferMapped) {
1080 vkUnmapMemory(device, instanceBufferMemory);
1081 instanceBufferMapped = nullptr;
1082 }
1083 vkDestroyBuffer(device, instanceBuffer, nullptr);
1084 vkFreeMemory(device, instanceBufferMemory, nullptr);
1085 instanceBuffer = VK_NULL_HANDLE;
1086 instanceBufferMemory = VK_NULL_HANDLE;
1087 instanceBufferCapacity = 0;
1088 }
1089 if (instancedPipeline != VK_NULL_HANDLE) {
1090 vkDestroyPipeline(device, instancedPipeline, nullptr);
1091 instancedPipeline = VK_NULL_HANDLE;
1092 }
1093 if (instancedPipelineLayout != VK_NULL_HANDLE) {
1094 vkDestroyPipelineLayout(device, instancedPipelineLayout, nullptr);
1095 instancedPipelineLayout = VK_NULL_HANDLE;
1096 }
1097 instancingEnabled = false;
1098 }
1099
1100 void VK_Sprite::ensureInstanceBuffer(uint32_t count) {
1101 if (instanceBufferCapacity >= count && instanceBuffer != VK_NULL_HANDLE)
1102 return;
1103
1104 if (instanceBuffer != VK_NULL_HANDLE) {
1105 if (instanceBufferMapped) {
1106 vkUnmapMemory(device, instanceBufferMemory);
1107 instanceBufferMapped = nullptr;
1108 }
1109 vkDestroyBuffer(device, instanceBuffer, nullptr);
1110 vkFreeMemory(device, instanceBufferMemory, nullptr);
1111 instanceBuffer = VK_NULL_HANDLE;
1112 instanceBufferMemory = VK_NULL_HANDLE;
1113 }
1114
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);
1117
1118 VK_CHECK_RESULT(vkMapMemory(device, instanceBufferMemory, 0, size, 0, &instanceBufferMapped));
1119 instanceBufferCapacity = count;
1120 }
1121
1122 void VK_Sprite::enableInstancing(uint32_t maxInstances, const std::string &instanceVertShaderPath, const std::string &instanceFragShaderPath) {
1123 if (colorAttachmentFormat == VK_FORMAT_UNDEFINED || descriptorSetLayout == VK_NULL_HANDLE) {
1124 throw mxvk::Exception("VKSprite::enableInstancing called before color format/descriptorSetLayout set");
1125 }
1126 ensureInstanceBuffer(maxInstances);
1127 createQuadBuffer();
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);
1133 }
1134
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;
1139 }
1140 if (instancedPipelineLayout != VK_NULL_HANDLE) {
1141 vkDestroyPipelineLayout(device, instancedPipelineLayout, nullptr);
1142 instancedPipelineLayout = VK_NULL_HANDLE;
1143 }
1144
1145 auto vertShaderCode = readShaderFile(vertPath);
1146 auto fragShaderCode = readShaderFile(fragPath);
1147 VkShaderModule vertModule = mxvk::create_shader_module(device, vertShaderCode);
1148 VkShaderModule fragModule = mxvk::create_shader_module(device, fragShaderCode);
1149
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";
1155
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";
1161
1162 VkPipelineShaderStageCreateInfo shaderStages[] = {vertStageInfo, fragStageInfo};
1163
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;
1171
1172 std::array<VkVertexInputAttributeDescription, 4> attrDescs{};
1173
1174 attrDescs[0].binding = 0;
1175 attrDescs[0].location = 0;
1176 attrDescs[0].format = VK_FORMAT_R32G32_SFLOAT;
1177 attrDescs[0].offset = 0;
1178
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;
1183
1184 attrDescs[2].binding = 1;
1185 attrDescs[2].location = 2;
1186 attrDescs[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
1187 attrDescs[2].offset = 0;
1188
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;
1193
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();
1200
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;
1205
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();
1211
1212 VkPipelineViewportStateCreateInfo viewportState{};
1213 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1214 viewportState.viewportCount = 1;
1215 viewportState.scissorCount = 1;
1216
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;
1226
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;
1231
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;
1236
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;
1246
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;
1252
1253 VkPushConstantRange pushConstantRange{};
1254 pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1255 pushConstantRange.offset = 0;
1256 pushConstantRange.size = sizeof(float) * 2;
1257
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;
1264
1265 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &instancedPipelineLayout));
1266
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;
1275 }
1276
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;
1293
1294 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo, nullptr, &instancedPipeline));
1295
1296 vkDestroyShaderModule(device, vertModule, nullptr);
1297 vkDestroyShaderModule(device, fragModule, nullptr);
1298 }
1299
1300 void VK_Sprite::createCustomPipeline() {
1301 if (!hasCustomShader || fragmentShaderModule == VK_NULL_HANDLE)
1302 return;
1303 if (colorAttachmentFormat == VK_FORMAT_UNDEFINED || descriptorSetLayout == VK_NULL_HANDLE)
1304 return;
1305
1306 if (customPipeline != VK_NULL_HANDLE) {
1307 vkDestroyPipeline(device, customPipeline, nullptr);
1308 customPipeline = VK_NULL_HANDLE;
1309 }
1310 if (customPipelineLayout != VK_NULL_HANDLE) {
1311 vkDestroyPipelineLayout(device, customPipelineLayout, nullptr);
1312 customPipelineLayout = VK_NULL_HANDLE;
1313 }
1314
1315 std::string vertPath = vertexShaderPath.empty() ? "sprite.vert.spv" : vertexShaderPath;
1316 auto vertShaderCode = readShaderFile(vertPath);
1317 VkShaderModule vertShaderModule = mxvk::create_shader_module(device, vertShaderCode);
1318
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";
1324
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";
1330
1331 VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
1332
1333 VkVertexInputBindingDescription bindingDescription{};
1334 bindingDescription.binding = 0;
1335 bindingDescription.stride = sizeof(float) * 4;
1336 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1337
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;
1347
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();
1354
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;
1359
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();
1365
1366 VkPipelineViewportStateCreateInfo viewportState{};
1367 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1368 viewportState.viewportCount = 1;
1369 viewportState.scissorCount = 1;
1370
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;
1380
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;
1385
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;
1390
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;
1400
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;
1406
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;
1411
1412 VkDescriptorSetLayout layoutToUseForPipeline = extendedUBOEnabled ? extendedDescriptorSetLayout : descriptorSetLayout;
1413
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;
1420
1421 VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &customPipelineLayout));
1422
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;
1431 }
1432
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;
1449
1450 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo, nullptr, &customPipeline));
1451
1452 vkDestroyShaderModule(device, vertShaderModule, nullptr);
1453 }
1454
1456 if (!hasCustomShader || fragmentShaderModule == VK_NULL_HANDLE)
1457 return;
1458 createCustomPipeline();
1459 std::cout << "mxvk: Pipeline rebuilt\n";
1460 }
1461
1462 void VK_Sprite::setFragmentShaderPath(const std::string &path) {
1463 if (path == fragmentShaderPath && fragmentShaderModule != VK_NULL_HANDLE) {
1464 return;
1465 }
1466
1467 if (customPipeline != VK_NULL_HANDLE) {
1468 vkDestroyPipeline(device, customPipeline, nullptr);
1469 customPipeline = VK_NULL_HANDLE;
1470 }
1471 if (customPipelineLayout != VK_NULL_HANDLE) {
1472 vkDestroyPipelineLayout(device, customPipelineLayout, nullptr);
1473 customPipelineLayout = VK_NULL_HANDLE;
1474 }
1475 if (fragmentShaderModule != VK_NULL_HANDLE) {
1476 vkDestroyShaderModule(device, fragmentShaderModule, nullptr);
1477 fragmentShaderModule = VK_NULL_HANDLE;
1478 }
1479
1480 fragmentShaderPath = path;
1481 hasCustomShader = false;
1482
1483 if (fragmentShaderPath.empty()) {
1484 return;
1485 }
1486
1487 const auto shaderCode = readShaderFile(fragmentShaderPath);
1488 fragmentShaderModule = mxvk::create_shader_module(device, shaderCode);
1489 hasCustomShader = true;
1490
1491 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
1492 createCustomPipeline();
1493 }
1494 }
1495
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;
1501 }
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;
1506 }
1507 }
1508
1509 void VK_Sprite::createComputePipeline() {
1510 if (computeShaderModule == VK_NULL_HANDLE || extendedDescriptorSetLayout == VK_NULL_HANDLE) {
1511 return;
1512 }
1513
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));
1520
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";
1526
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";
1533 }
1534
1535 void VK_Sprite::enableComputeShader(const std::string &path, uint32_t localSizeX, uint32_t localSizeY, uint32_t localSizeZ) {
1536 if (path.empty() || localSizeX == 0 || localSizeY == 0 || localSizeZ == 0) {
1537 throw mxvk::Exception("VKSprite::enableComputeShader requires a shader and positive local size");
1538 }
1539
1540 vkDeviceWaitIdle(device);
1541 destroyComputePipeline();
1542 if (computeShaderModule != VK_NULL_HANDLE) {
1543 vkDestroyShaderModule(device, computeShaderModule, nullptr);
1544 computeShaderModule = VK_NULL_HANDLE;
1545 }
1546 computeShaderModule = mxvk::create_shader_module(device, readShaderFile(path));
1547 computeLocalSizeX = localSizeX;
1548 computeLocalSizeY = localSizeY;
1549
1550 if (!extendedUBOEnabled) {
1552 createComputePipeline();
1553 } else {
1554 recreateExtendedDescriptorLayout();
1555 }
1556 }
1557
1558 void VK_Sprite::dispatchCompute(VkCommandBuffer cmdBuffer, VkImageView inputView, VkImageView outputView, uint32_t width, uint32_t height) {
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");
1561 }
1562
1563 setExternalTexture(inputView, static_cast<int>(width), static_cast<int>(height));
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;
1570 }
1571 computeOutputImageView = outputView;
1572 }
1573 updateExtendedUBO();
1574 if (extendedDescriptorSet == VK_NULL_HANDLE) {
1575 createExtendedDescriptorSet();
1576 }
1577 if (extendedDescriptorSet == VK_NULL_HANDLE) {
1578 throw mxvk::Exception("VKSprite::dispatchCompute could not create its descriptor set");
1579 }
1580
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);
1584 }
1585
1587 if (!instancingEnabled || instanceVertPath.empty() || instanceFragPath.empty())
1588 return;
1589 createInstancedPipeline(instanceVertPath, instanceFragPath);
1590 }
1591
1592 void VK_Sprite::createQuadBuffer() {
1593 if (quadBufferCreated)
1594 return;
1595
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};
1598
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);
1601
1602 void *data;
1603 VK_CHECK_RESULT(vkMapMemory(device, quadVertexBufferMemory, 0, vertexSize, 0, &data));
1604 memcpy(data, vertices, vertexSize);
1605 vkUnmapMemory(device, quadVertexBufferMemory);
1606
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);
1609
1610 VK_CHECK_RESULT(vkMapMemory(device, quadIndexBufferMemory, 0, indexSize, 0, &data));
1611 memcpy(data, indices, indexSize);
1612 vkUnmapMemory(device, quadIndexBufferMemory);
1613
1614 quadBufferCreated = true;
1615 }
1616
1617 void VK_Sprite::loadSprite(const std::string &pngPath, const std::string &fragmentShaderPath) {
1618 SDL_Surface *surface = mxvk::LoadPNG(pngPath.c_str());
1619 if (!surface) {
1620 throw mxvk::Exception("Failed to load sprite image: " + pngPath);
1621 }
1622 loadSprite(surface, fragmentShaderPath);
1623 SDL_DestroySurface(surface);
1624 std::cout << std::format("mxvk: Loaded PNG: {}\n", pngPath);
1625 }
1626
1627 void VK_Sprite::loadSprite(SDL_Surface *surface, const std::string &fragmentShaderPath) {
1628 if (!surface) {
1629 throw mxvk::Exception("VKSprite::loadSprite called with null surface");
1630 }
1631 if (spriteLoaded || spriteImage != VK_NULL_HANDLE || fragmentShaderModule != VK_NULL_HANDLE) {
1632 destroySpriteResources();
1633 }
1634 SDL_Surface *rgbaSurface = convertToRGBA(surface);
1635 if (!rgbaSurface) {
1636 throw mxvk::Exception("Failed to convert sprite surface to RGBA");
1637 }
1638 spriteWidth = rgbaSurface->w;
1639 spriteHeight = rgbaSurface->h;
1640 createSpriteTexture(rgbaSurface);
1641 SDL_DestroySurface(rgbaSurface);
1642 createSampler();
1643 createQuadBuffer();
1644 if (!fragmentShaderPath.empty()) {
1645 auto shaderCode = readShaderFile(fragmentShaderPath);
1646 fragmentShaderModule = mxvk::create_shader_module(device, shaderCode);
1647 hasCustomShader = true;
1648 this->fragmentShaderPath = fragmentShaderPath;
1649
1650 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
1651 createCustomPipeline();
1652 }
1653 }
1654 spriteLoaded = true;
1655 std::cout << std::format("mxvk: Loaded surface texture: {}x{}\n", spriteWidth, spriteHeight);
1656 }
1657
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); }
1659
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); }
1661
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");
1665 }
1666 if (bytesPerPixel == 0) {
1667 throw mxvk::Exception("VKSprite::createEmptySprite invalid pixel size");
1668 }
1669 if (spriteLoaded || spriteImage != VK_NULL_HANDLE || fragmentShaderModule != VK_NULL_HANDLE) {
1670 destroySpriteResources();
1671 }
1672 spriteWidth = width;
1673 spriteHeight = height;
1674 spriteImageFormat = format;
1675 spriteBytesPerPixel = bytesPerPixel;
1676
1677 if (!vertexShaderPath.empty()) {
1678 setVertexShaderPath(vertexShaderPath);
1679 }
1680
1681#ifdef MXVK_CUDA
1682 if (format == VK_FORMAT_R8G8B8A8_UNORM) {
1683 try {
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);
1689 }
1690 } else {
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);
1692 }
1693#else
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);
1695#endif
1696
1697 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1698 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
1699 VkDeviceSize imageSize = static_cast<VkDeviceSize>(width) * height * bytesPerPixel;
1700
1701 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
1702
1703 void *data;
1704 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
1705 memset(data, 0, imageSize);
1706 vkUnmapMemory(device, stagingMemory);
1707
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);
1711#ifdef MXVK_CUDA
1712 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1713#endif
1714
1715 vkDestroyBuffer(device, stagingBuffer, nullptr);
1716 vkFreeMemory(device, stagingMemory, nullptr);
1717
1718 spriteImageView = createImageView(spriteImage, format);
1719 createSampler();
1720 createQuadBuffer();
1721 createDescriptorPool();
1722
1723 createStagingResources(imageSize);
1724
1725 if (!fragmentShaderPath.empty()) {
1726 auto shaderCode = readShaderFile(fragmentShaderPath);
1727 fragmentShaderModule = mxvk::create_shader_module(device, shaderCode);
1728 hasCustomShader = true;
1729 this->fragmentShaderPath = fragmentShaderPath;
1730
1731 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
1732 createCustomPipeline();
1733 }
1734 }
1735
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");
1738 }
1739
1741 if (!surface) {
1742 throw mxvk::Exception("VKSprite::updateTexture called with null surface");
1743 }
1744 if (!spriteLoaded) {
1745 throw mxvk::Exception("VKSprite::updateTexture called before sprite was loaded");
1746 }
1747 SDL_Surface *rgbaSurface = convertToRGBA(surface);
1748 if (!rgbaSurface) {
1749 throw mxvk::Exception("Failed to convert surface to RGBA in updateTexture");
1750 }
1751 if (rgbaSurface->w == spriteWidth && rgbaSurface->h == spriteHeight) {
1752#ifdef MXVK_CUDA
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);
1755 return;
1756 }
1757#endif
1758 updateSpriteTexture(rgbaSurface->pixels, rgbaSurface->w, rgbaSurface->h);
1759 } else {
1760 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
1761 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1762 }
1763#ifdef MXVK_CUDA
1764 destroyCudaInterop();
1765#endif
1766 destroyTextureDescriptorPools();
1767 if (spriteImageView != VK_NULL_HANDLE) {
1768 vkDestroyImageView(device, spriteImageView, nullptr);
1769 spriteImageView = VK_NULL_HANDLE;
1770 }
1771 if (spriteImage != VK_NULL_HANDLE) {
1772 vkDestroyImage(device, spriteImage, nullptr);
1773 spriteImage = VK_NULL_HANDLE;
1774 }
1775 if (spriteImageMemory != VK_NULL_HANDLE) {
1776 vkFreeMemory(device, spriteImageMemory, nullptr);
1777 spriteImageMemory = VK_NULL_HANDLE;
1778 }
1779 spriteWidth = rgbaSurface->w;
1780 spriteHeight = rgbaSurface->h;
1781 createSpriteTexture(rgbaSurface);
1782 createDescriptorPool();
1783 }
1784 SDL_DestroySurface(rgbaSurface);
1785 }
1786
1787 void VK_Sprite::updateTexture(const void *pixels, int width, int height, int pitch) {
1788 if (!pixels) {
1789 throw mxvk::Exception("VKSprite::updateTexture called with null pixel data");
1790 }
1791 if (!spriteLoaded) {
1792 throw mxvk::Exception("VKSprite::updateTexture called before sprite was loaded");
1793 }
1794 if (width <= 0 || height <= 0) {
1795 throw mxvk::Exception("VKSprite::updateTexture invalid dimensions");
1796 }
1797 if (spriteImageFormat != VK_FORMAT_R8G8B8A8_UNORM || spriteBytesPerPixel != 4) {
1798 throw mxvk::Exception("VKSprite::updateTexture cannot update an RGBA16 sprite; use "
1799 "updateTextureRgba16");
1800 }
1801 int srcPitch = (pitch > 0) ? pitch : width * 4;
1802 if (width == spriteWidth && height == spriteHeight && srcPitch == width * 4) {
1803#ifdef MXVK_CUDA
1804 if (updateTextureCudaHost(pixels, static_cast<uint32_t>(width), static_cast<uint32_t>(height), static_cast<uint32_t>(srcPitch))) {
1805 return;
1806 }
1807#endif
1808 updateSpriteTexture(pixels, width, height);
1809 } else if (width == spriteWidth && height == spriteHeight) {
1810#ifdef MXVK_CUDA
1811 if (updateTextureCudaHost(pixels, static_cast<uint32_t>(width), static_cast<uint32_t>(height), static_cast<uint32_t>(srcPitch))) {
1812 return;
1813 }
1814#endif
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);
1819 }
1820 updateSpriteTexture(packed.data(), width, height);
1821 } else {
1822 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
1823 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1824 }
1825#ifdef MXVK_CUDA
1826 destroyCudaInterop();
1827#endif
1828 destroyTextureDescriptorPools();
1829 if (spriteImageView != VK_NULL_HANDLE) {
1830 vkDestroyImageView(device, spriteImageView, nullptr);
1831 spriteImageView = VK_NULL_HANDLE;
1832 }
1833 if (spriteImage != VK_NULL_HANDLE) {
1834 vkDestroyImage(device, spriteImage, nullptr);
1835 spriteImage = VK_NULL_HANDLE;
1836 }
1837 if (spriteImageMemory != VK_NULL_HANDLE) {
1838 vkFreeMemory(device, spriteImageMemory, nullptr);
1839 spriteImageMemory = VK_NULL_HANDLE;
1840 }
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);
1850 }
1851 texData = packed.data();
1852 }
1853 // Resize path: wrap raw pixels in a temporary SDL3 surface (no copy)
1854 SDL_Surface *tmpSurface = SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_RGBA32, const_cast<void *>(texData), width * 4);
1855 if (!tmpSurface) {
1856 throw mxvk::Exception("VKSprite::updateTexture failed to create temp surface");
1857 }
1858 createSpriteTexture(tmpSurface);
1859 SDL_DestroySurface(tmpSurface);
1860 createDescriptorPool();
1861 }
1862 }
1863
1864 void VK_Sprite::updateTextureRgba16(const uint16_t *pixels, int width, int height, int pitch) {
1865 if (pixels == nullptr) {
1866 throw mxvk::Exception("VKSprite::updateTextureRgba16 called with null pixel data");
1867 }
1868 if (!spriteLoaded || spriteImageFormat != VK_FORMAT_R16G16B16A16_UNORM || spriteBytesPerPixel != 8) {
1869 throw mxvk::Exception("VKSprite::updateTextureRgba16 requires an RGBA16 sprite");
1870 }
1871 if (width != spriteWidth || height != spriteHeight || width <= 0 || height <= 0) {
1872 throw mxvk::Exception("VKSprite::updateTextureRgba16 dimensions do not match");
1873 }
1874
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");
1879 }
1880 if (sourcePitch == rowBytes) {
1881 updateSpriteTexture(pixels, static_cast<uint32_t>(width), static_cast<uint32_t>(height));
1882 return;
1883 }
1884
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));
1890 }
1891 updateSpriteTexture(packed.data(), static_cast<uint32_t>(width), static_cast<uint32_t>(height));
1892 }
1893
1894 void VK_Sprite::updateSpriteTexture(const void *pixels, uint32_t width, uint32_t height) {
1895 VkDeviceSize imageSize = static_cast<VkDeviceSize>(width) * height * spriteBytesPerPixel;
1896
1897 createStagingResources(imageSize);
1898 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
1899 VK_CHECK_RESULT(vkResetFences(device, 1, &uploadFence));
1900 memcpy(persistentStagingMapped, pixels, imageSize);
1901 VK_CHECK_RESULT(vkResetCommandBuffer(uploadCmdBuffer, 0));
1902 VkCommandBufferBeginInfo beginInfo{};
1903 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1904 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1905 VK_CHECK_RESULT(vkBeginCommandBuffer(uploadCmdBuffer, &beginInfo));
1906 VkImageMemoryBarrier barrier{};
1907 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1908 VkImageLayout oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1909#ifdef MXVK_CUDA
1910 if (cudaImageLayout != VK_IMAGE_LAYOUT_UNDEFINED) {
1911 oldLayout = cudaImageLayout;
1912 }
1913#endif
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);
1928
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, &region);
1940
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);
1946
1947 VK_CHECK_RESULT(vkEndCommandBuffer(uploadCmdBuffer));
1948
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));
1954#ifdef MXVK_CUDA
1955 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1956 cudaImageNeedsShaderBarrier = false;
1957#endif
1958 }
1959
1960#ifdef MXVK_CUDA
1961 void VK_Sprite::destroyCudaInterop() {
1962 if (cudaInteropEnabled || cudaExternalMemory != nullptr || cudaMipmappedArray != nullptr) {
1963 std::cout << "mxvk: CUDA interop: destroying imported Vulkan texture resources\n";
1964 }
1965 if (cudaMipmappedArray != nullptr) {
1966 cudaFreeMipmappedArray(cudaMipmappedArray);
1967 cudaMipmappedArray = nullptr;
1968 cudaArray = nullptr;
1969 }
1970 if (cudaExternalMemory != nullptr) {
1971 cudaDestroyExternalMemory(cudaExternalMemory);
1972 cudaExternalMemory = nullptr;
1973 }
1974 cudaInteropEnabled = false;
1975 cudaImageNeedsShaderBarrier = false;
1976 cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1977 cudaExportMemorySize = 0;
1978 cudaUploadLogged = false;
1979 cudaWriteTransitionLogged = false;
1980 cudaSampleBarrierLogged = false;
1981 }
1982
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",
1986 width,
1987 height,
1988 arrayLayers);
1989 if (image != VK_NULL_HANDLE) {
1990 vkDestroyImage(device, image, nullptr);
1991 image = VK_NULL_HANDLE;
1992 }
1993 if (imageMemory != VK_NULL_HANDLE) {
1994 vkFreeMemory(device, imageMemory, nullptr);
1995 imageMemory = VK_NULL_HANDLE;
1996 }
1997
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;
2001
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;
2017
2018 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &image));
2019
2020 VkMemoryRequirements memRequirements{};
2021 vkGetImageMemoryRequirements(device, image, &memRequirements);
2022
2023 VkExportMemoryAllocateInfo exportMemoryInfo{};
2024 exportMemoryInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
2025 exportMemoryInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
2026
2027 VkMemoryAllocateInfo allocInfo{};
2028 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
2029 allocInfo.pNext = &exportMemoryInfo;
2030 allocInfo.allocationSize = memRequirements.size;
2031
2032 try {
2033 allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
2034 VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory));
2035 VK_CHECK_RESULT(vkBindImageMemory(device, image, imageMemory, 0));
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);
2038 } catch (...) {
2039 if (imageMemory != VK_NULL_HANDLE) {
2040 vkFreeMemory(device, imageMemory, nullptr);
2041 imageMemory = VK_NULL_HANDLE;
2042 }
2043 if (image != VK_NULL_HANDLE) {
2044 vkDestroyImage(device, image, nullptr);
2045 image = VK_NULL_HANDLE;
2046 }
2047 exportMemorySize = 0;
2048 throw;
2049 }
2050 }
2051
2052 bool VK_Sprite::ensureCudaInterop() {
2053 if (cudaInteropEnabled) {
2054 return true;
2055 }
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;
2060 }
2061 return false;
2062 }
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;
2067 }
2068 return false;
2069 }
2070
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;
2075
2076 int memoryFd = -1;
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;
2082 }
2083 return false;
2084 }
2085 std::cout << std::format("mxvk: CUDA interop init: exported Vulkan image memory fd={}\n", memoryFd);
2086
2087 cudaExternalMemoryHandleDesc externalMemoryDesc{};
2088 externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueFd;
2089 externalMemoryDesc.handle.fd = memoryFd;
2090 externalMemoryDesc.size = cudaExportMemorySize;
2091
2092 cudaError_t cudaResult = cudaImportExternalMemory(&cudaExternalMemory, &externalMemoryDesc);
2093 if (cudaResult != cudaSuccess) {
2094 close(memoryFd);
2095 if (!cudaInteropUnavailableLogged) {
2096 std::cout << std::format("mxvk: CUDA interop init: cudaImportExternalMemory failed: {}\n", cudaGetErrorString(cudaResult));
2097 cudaInteropUnavailableLogged = true;
2098 }
2099 cudaExternalMemory = nullptr;
2100 return false;
2101 }
2102 std::cout << std::format("mxvk: CUDA interop init: imported external memory into CUDA ({} bytes)\n", static_cast<unsigned long long>(cudaExportMemorySize));
2103
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;
2110
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;
2116 }
2117 destroyCudaInterop();
2118 return false;
2119 }
2120 std::cout << std::format("mxvk: CUDA interop init: mapped CUDA mipmapped array {}x{} uchar4\n", spriteWidth, spriteHeight);
2121
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;
2127 }
2128 destroyCudaInterop();
2129 return false;
2130 }
2131
2132 cudaInteropEnabled = true;
2133 std::cout << "mxvk: CUDA interop init: direct CUDA-to-Vulkan texture upload is ready\n";
2134 return true;
2135 }
2136
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";
2141 }
2142 if (cudaHistoryMipmappedArray != nullptr) {
2143 cudaFreeMipmappedArray(cudaHistoryMipmappedArray);
2144 cudaHistoryMipmappedArray = nullptr;
2145 cudaHistoryArray = nullptr;
2146 }
2147 if (cudaHistoryExternalMemory != nullptr) {
2148 cudaDestroyExternalMemory(cudaHistoryExternalMemory);
2149 cudaHistoryExternalMemory = nullptr;
2150 }
2151 cudaHistoryInteropEnabled = false;
2152 cudaHistoryExportMemorySize = 0;
2153 cudaHistoryUploadLogged = false;
2154 }
2155
2156 bool VK_Sprite::ensureCudaHistoryInterop() {
2157 if (cudaHistoryInteropEnabled) {
2158 return true;
2159 }
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 "
2163 "exportable\n";
2164 cudaHistoryInteropUnavailableLogged = true;
2165 }
2166 return false;
2167 }
2168 if (vkGetMemoryFdKHR == nullptr) {
2169 if (!cudaHistoryInteropUnavailableLogged) {
2170 std::cout << "mxvk: CUDA history interop: vkGetMemoryFdKHR was "
2171 "not loaded\n";
2172 cudaHistoryInteropUnavailableLogged = true;
2173 }
2174 return false;
2175 }
2176
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;
2181
2182 int memoryFd = -1;
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 "
2187 "({})\n",
2188 static_cast<int>(fdResult));
2189 cudaHistoryInteropUnavailableLogged = true;
2190 }
2191 return false;
2192 }
2193
2194 cudaExternalMemoryHandleDesc externalMemoryDesc{};
2195 externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueFd;
2196 externalMemoryDesc.handle.fd = memoryFd;
2197 externalMemoryDesc.size = cudaHistoryExportMemorySize;
2198
2199 cudaError_t cudaResult = cudaImportExternalMemory(&cudaHistoryExternalMemory, &externalMemoryDesc);
2200 if (cudaResult != cudaSuccess) {
2201 close(memoryFd);
2202 if (!cudaHistoryInteropUnavailableLogged) {
2203 std::cout << std::format("mxvk: CUDA history interop: import failed: {}\n", cudaGetErrorString(cudaResult));
2204 cudaHistoryInteropUnavailableLogged = true;
2205 }
2206 cudaHistoryExternalMemory = nullptr;
2207 return false;
2208 }
2209
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;
2216
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;
2222 }
2223 destroyCudaHistoryInterop();
2224 return false;
2225 }
2226
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;
2232 }
2233 destroyCudaHistoryInterop();
2234 return false;
2235 }
2236
2237 cudaHistoryInteropEnabled = true;
2238 cudaHistoryInteropUnavailableLogged = false;
2239 std::cout << std::format("mxvk: CUDA history interop: direct {}-layer upload is ready\n", historyLayers);
2240 return true;
2241 }
2242
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);
2261 }
2262
2263 bool VK_Sprite::transitionCudaImageForWrite() {
2264 if (cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) {
2265 return true;
2266 }
2267
2268 const VkImageLayout oldLayout = (cudaImageLayout == VK_IMAGE_LAYOUT_UNDEFINED) ? VK_IMAGE_LAYOUT_UNDEFINED : cudaImageLayout;
2269 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2270
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;
2285
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);
2289
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;
2294 }
2295 return true;
2296 }
2297
2298 bool VK_Sprite::transitionCudaImageForShaderRead() {
2299 if (cudaImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && !cudaImageNeedsShaderBarrier) {
2300 return true;
2301 }
2302
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;
2318
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);
2321
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;
2327 }
2328 return true;
2329 }
2330
2331 void VK_Sprite::recordCudaReadyBarrier(VkCommandBuffer cmdBuffer) {
2332 if (!cudaImageNeedsShaderBarrier) {
2333 return;
2334 }
2335
2336 (void)cmdBuffer;
2337 transitionCudaImageForShaderRead();
2338 }
2339
2340 bool VK_Sprite::updateTextureCuda(const cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream) {
2341 if (!spriteLoaded) {
2342 return false;
2343 }
2344 if (spriteImageFormat != VK_FORMAT_R8G8B8A8_UNORM || spriteBytesPerPixel != 4 || rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols <= 0 || rgba.rows <= 0) {
2345 return false;
2346 }
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);
2350 }
2351 vkDeviceWaitIdle(device);
2352 destroyCudaInterop();
2353 destroyTextureDescriptorPools();
2354 if (spriteImageView != VK_NULL_HANDLE) {
2355 vkDestroyImageView(device, spriteImageView, nullptr);
2356 spriteImageView = VK_NULL_HANDLE;
2357 }
2358 if (spriteImage != VK_NULL_HANDLE) {
2359 vkDestroyImage(device, spriteImage, nullptr);
2360 spriteImage = VK_NULL_HANDLE;
2361 }
2362 if (spriteImageMemory != VK_NULL_HANDLE) {
2363 vkFreeMemory(device, spriteImageMemory, nullptr);
2364 spriteImageMemory = VK_NULL_HANDLE;
2365 }
2366
2367 spriteWidth = rgba.cols;
2368 spriteHeight = rgba.rows;
2369 try {
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;
2377 }
2378 return false;
2379 }
2380
2381 createDescriptorPool();
2382 if (spriteSampler == VK_NULL_HANDLE) {
2383 createSampler();
2384 }
2385 createQuadBuffer();
2386 }
2387 if (!ensureCudaInterop() || !transitionCudaImageForWrite()) {
2388 return false;
2389 }
2390
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;
2395 }
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));
2399 return false;
2400 }
2401
2402 cudaResult = cudaStreamSynchronize(cudaStream);
2403 if (cudaResult != cudaSuccess) {
2404 std::cout << std::format("mxvk: CUDA interop texture sync failed: {}\n", cudaGetErrorString(cudaResult));
2405 return false;
2406 }
2407
2408 cudaImageNeedsShaderBarrier = true;
2409 return transitionCudaImageForShaderRead();
2410 }
2411
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()) {
2414 return false;
2415 }
2416
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);
2418
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;
2425
2426 cudaStream_t cudaStream = cuda_stream_handle(stream);
2427 cudaError_t cudaResult = cudaMemcpy3DAsync(&copyParameters, cudaStream);
2428 if (cudaResult == cudaSuccess) {
2429 cudaResult = cudaStreamSynchronize(cudaStream);
2430 }
2431
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);
2433
2434 if (cudaResult != cudaSuccess) {
2435 std::cout << std::format("mxvk: CUDA history interop upload failed: {}\n", cudaGetErrorString(cudaResult));
2436 return false;
2437 }
2438 if (!cudaHistoryUploadLogged) {
2439 std::cout << std::format("mxvk: CUDA history interop: copying {}x{} RGBA GpuMat into "
2440 "the Vulkan history array\n",
2441 rgba.cols,
2442 rgba.rows);
2443 cudaHistoryUploadLogged = true;
2444 }
2445 historyHead = (historyHead + 1) % historyLayers;
2446 return true;
2447 }
2448
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) {
2451 return false;
2452 }
2453 const uint32_t rowBytes = width * 4U;
2454 if (pitch < rowBytes || static_cast<int>(width) != spriteWidth || static_cast<int>(height) != spriteHeight) {
2455 return false;
2456 }
2457 if (!ensureCudaInterop() || !transitionCudaImageForWrite()) {
2458 return false;
2459 }
2460
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;
2464 }
2465
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));
2469 return false;
2470 }
2471
2472 cudaImageNeedsShaderBarrier = true;
2473 return transitionCudaImageForShaderRead();
2474 }
2475#endif
2476
2477 void VK_Sprite::createSpriteTexture(SDL_Surface *surface) {
2478 spriteImageFormat = VK_FORMAT_R8G8B8A8_UNORM;
2479 spriteBytesPerPixel = 4;
2480#ifdef MXVK_CUDA
2481 try {
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);
2487 }
2488#else
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);
2490#endif
2491
2492#ifdef MXVK_CUDA
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);
2495 return;
2496 }
2497#endif
2498
2499 VkBuffer stagingBuffer = VK_NULL_HANDLE;
2500 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
2501 VkDeviceSize imageSize = static_cast<VkDeviceSize>(surface->w) * surface->h * 4;
2502
2503 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
2504
2505 void *data;
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);
2510 } else {
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);
2515 }
2516 vkUnmapMemory(device, stagingMemory);
2517
2518#ifdef MXVK_CUDA
2519 const VkImageLayout uploadOldLayout = (cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED;
2520#else
2521 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2522#endif
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);
2526#ifdef MXVK_CUDA
2527 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2528#endif
2529
2530 vkDestroyBuffer(device, stagingBuffer, nullptr);
2531 vkFreeMemory(device, stagingMemory, nullptr);
2532
2533 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
2534 }
2535
2536 void VK_Sprite::createSampler() {
2537 if (spriteSampler != VK_NULL_HANDLE) {
2538 vkDestroySampler(device, spriteSampler, nullptr);
2539 spriteSampler = VK_NULL_HANDLE;
2540 }
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;
2557
2558 VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &spriteSampler));
2559 }
2560
2561 void VK_Sprite::drawSprite(int x, int y) { drawSpriteRect(x, y, spriteWidth, spriteHeight); }
2562
2563 void VK_Sprite::drawSprite(int x, int y, float scaleX, float scaleY) { drawSpriteRect(x, y, static_cast<int>(spriteWidth * scaleX), static_cast<int>(spriteHeight * scaleY)); }
2564
2565 void VK_Sprite::drawSprite(int x, int y, float scaleX, float scaleY, float rotation) {
2566 if (!spriteLoaded) {
2567 throw mxvk::Exception("VKSprite::drawSprite called before sprite was loaded");
2568 }
2569
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});
2571 }
2572
2573 void VK_Sprite::drawSpriteRect(int x, int y, int w, int h) {
2574 if (!spriteLoaded) {
2575 throw mxvk::Exception("VKSprite::drawSpriteRect called before sprite was loaded");
2576 }
2577
2578 drawQueue.push_back({static_cast<float>(x), static_cast<float>(y), static_cast<float>(w), static_cast<float>(h), 0.0f, shaderParams});
2579 }
2580
2581 void VK_Sprite::setShaderParams(float p1, float p2, float p3, float p4) { shaderParams = glm::vec4(p1, p2, p3, p4); }
2582
2583 void VK_Sprite::setExternalTexture(VkImageView image_view, int width, int height) {
2584 if (image_view == VK_NULL_HANDLE || width <= 0 || height <= 0) {
2585 throw mxvk::Exception("VKSprite::setExternalTexture received an invalid image view");
2586 }
2587 if (externalTexture && spriteImageView == image_view) {
2588 spriteWidth = width;
2589 spriteHeight = height;
2590 spriteLoaded = true;
2591 return;
2592 }
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;
2603 }
2604 if (!externalTexture && spriteImageView != VK_NULL_HANDLE) {
2605 vkDestroyImageView(device, spriteImageView, nullptr);
2606 }
2607 if (!externalTexture && spriteImage != VK_NULL_HANDLE) {
2608 vkDestroyImage(device, spriteImage, nullptr);
2609 }
2610 if (!externalTexture && spriteImageMemory != VK_NULL_HANDLE) {
2611 vkFreeMemory(device, spriteImageMemory, nullptr);
2612 }
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;
2620 }
2621
2623 if (!externalTexture && externalDescriptorSets.empty()) {
2624 return;
2625 }
2626 destroyTextureDescriptorPools();
2627 }
2628
2629 void VK_Sprite::prepareForRendering([[maybe_unused]] VkCommandBuffer cmdBuffer) {
2630#ifdef MXVK_CUDA
2631 recordCudaReadyBarrier(cmdBuffer);
2632#endif
2633 }
2634
2635 void VK_Sprite::renderSprites(VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout, uint32_t screenWidth, uint32_t screenHeight) {
2636 if (drawQueue.empty() || !spriteLoaded || !quadBufferCreated) {
2637 return;
2638 }
2639 if (descriptorSet == VK_NULL_HANDLE) {
2640 descriptorSet = createDescriptorSet(spriteImageView);
2641 if (externalTexture) {
2642 externalDescriptorSets[spriteImageView] = descriptorSet;
2643 }
2644 }
2645
2646 if (instancingEnabled && instancedPipeline != VK_NULL_HANDLE && instanceBuffer != VK_NULL_HANDLE) {
2647 uint32_t instanceCount = static_cast<uint32_t>(drawQueue.size());
2648
2649 if (instanceCount > instanceBufferCapacity) {
2650 ensureInstanceBuffer(instanceCount * 2);
2651 }
2652
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;
2664 }
2665
2666 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, instancedPipeline);
2667 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, instancedPipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
2668
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);
2673
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);
2676
2677 vkCmdDrawIndexed(cmdBuffer, 6, instanceCount, 0, 0, 0);
2678 return;
2679 }
2680
2681 VkPipelineLayout layoutToUse = (customPipeline != VK_NULL_HANDLE) ? customPipelineLayout : pipelineLayout;
2682 if (customPipeline != VK_NULL_HANDLE) {
2683 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, customPipeline);
2684 }
2685
2686 // When extended UBO is enabled, update UBO and bind extended descriptor set
2687 if (extendedUBOEnabled && customPipeline != VK_NULL_HANDLE) {
2688 updateExtendedUBO();
2689 if (extendedDescriptorSet == VK_NULL_HANDLE) {
2690 createExtendedDescriptorSet();
2691 }
2692 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layoutToUse, 0, 1, &extendedDescriptorSet, 0, nullptr);
2693 } else {
2694 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layoutToUse, 0, 1, &descriptorSet, 0, nullptr);
2695 }
2696
2697 VkBuffer vertexBuffers[] = {quadVertexBuffer};
2698 VkDeviceSize offsets[] = {0};
2699 vkCmdBindVertexBuffers(cmdBuffer, 0, 1, vertexBuffers, offsets);
2700 vkCmdBindIndexBuffer(cmdBuffer, quadIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
2701
2702 for (const auto &cmd : drawQueue) {
2703 struct SpritePushConstants {
2704 float screenWidth;
2705 float screenHeight;
2706 float spritePosX;
2707 float spritePosY;
2708 float spriteSizeW;
2709 float spriteSizeH;
2710 float effectsOn;
2711 float padding2;
2712 float params[4];
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}};
2714
2715 vkCmdPushConstants(cmdBuffer, layoutToUse, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(SpritePushConstants), &pc);
2716
2717 vkCmdDrawIndexed(cmdBuffer, 6, 1, 0, 0, 0);
2718 }
2719 }
2720
2721 void VK_Sprite::clearQueue() { drawQueue.clear(); }
2722
2723 void VK_Sprite::createDescriptorPool() {
2724 VkDescriptorPoolSize poolSize{};
2725 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
2726 poolSize.descriptorCount = nextDescriptorPoolSets;
2727
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;
2734
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;
2739 }
2740 }
2741
2742 void VK_Sprite::destroyDescriptorPools() {
2743 for (VkDescriptorPool pool : descriptorPools) {
2744 if (pool != VK_NULL_HANDLE) {
2745 vkDestroyDescriptorPool(device, pool, nullptr);
2746 }
2747 }
2748 descriptorPools.clear();
2749 descriptorPool = VK_NULL_HANDLE;
2750 descriptorSetPool = VK_NULL_HANDLE;
2751 descriptorSet = VK_NULL_HANDLE;
2752 externalDescriptorSets.clear();
2753 nextDescriptorPoolSets = 16;
2754 }
2755
2756 void VK_Sprite::destroyTextureDescriptorPools() {
2757 if (!descriptorPools.empty() || extendedDescriptorPool != VK_NULL_HANDLE) {
2758 vkDeviceWaitIdle(device);
2759 }
2760 destroyDescriptorPools();
2761 if (extendedDescriptorPool != VK_NULL_HANDLE) {
2762 vkDestroyDescriptorPool(device, extendedDescriptorPool, nullptr);
2763 extendedDescriptorPool = VK_NULL_HANDLE;
2764 extendedDescriptorSet = VK_NULL_HANDLE;
2765 }
2766 }
2767
2768 VkDescriptorSet VK_Sprite::createDescriptorSet(VkImageView imageView) {
2769 if (descriptorSetLayout == VK_NULL_HANDLE) {
2770 throw mxvk::Exception("VKSprite::createDescriptorSet called before setDescriptorSetLayout");
2771 }
2772
2773 if (descriptorPool == VK_NULL_HANDLE) {
2774 createDescriptorPool();
2775 }
2776
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;
2782
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);
2789 }
2790 if (allocateResult != VK_SUCCESS) {
2791 throw mxvk::Exception(std::format("Fatal : VkResult is \"{}\" in {} at line {}", static_cast<int>(allocateResult), __FILE__, __LINE__));
2792 }
2793 descriptorSetPool = allocInfo.descriptorPool;
2794
2795 VkDescriptorImageInfo imageInfo{};
2796 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2797 imageInfo.imageView = imageView;
2798 imageInfo.sampler = spriteSampler;
2799
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;
2808
2809 vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
2810
2811 return descSet;
2812 }
2813
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;
2817
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;
2823
2824 try {
2825 VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &newBuffer));
2826
2827 VkMemoryRequirements memRequirements;
2828 vkGetBufferMemoryRequirements(device, newBuffer, &memRequirements);
2829
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));
2835 VK_CHECK_RESULT(vkBindBufferMemory(device, newBuffer, newMemory, 0));
2836 } catch (...) {
2837 if (newBuffer != VK_NULL_HANDLE) {
2838 vkDestroyBuffer(device, newBuffer, nullptr);
2839 }
2840 if (newMemory != VK_NULL_HANDLE) {
2841 vkFreeMemory(device, newMemory, nullptr);
2842 }
2843 throw;
2844 }
2845
2846 if (buffer != VK_NULL_HANDLE) {
2847 vkDestroyBuffer(device, buffer, nullptr);
2848 }
2849 if (bufferMemory != VK_NULL_HANDLE) {
2850 vkFreeMemory(device, bufferMemory, nullptr);
2851 }
2852 buffer = newBuffer;
2853 bufferMemory = newMemory;
2854 }
2855
2856 uint32_t VK_Sprite::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
2857 VkPhysicalDeviceMemoryProperties memProperties;
2858 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
2859
2860 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
2861 if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
2862 return i;
2863 }
2864 }
2865
2866 throw mxvk::Exception("Failed to find suitable memory type!");
2867 }
2868
2869 void VK_Sprite::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t baseArrayLayer, uint32_t layerCount) {
2870 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
2871
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;
2884
2885 VkPipelineStageFlags sourceStage;
2886 VkPipelineStageFlags destinationStage;
2887
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;
2908 } else {
2909 throw std::invalid_argument("unsupported layout transition!");
2910 }
2911
2912 vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
2913 endSingleTimeCommands(commandBuffer);
2914 }
2915
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();
2918
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};
2929
2930 vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
2931
2932 endSingleTimeCommands(commandBuffer);
2933 }
2934
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;
2941
2942 VkCommandBuffer commandBuffer;
2943 VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
2944
2945 VkCommandBufferBeginInfo beginInfo{};
2946 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
2947 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
2948
2949 VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &beginInfo));
2950
2951 return commandBuffer;
2952 }
2953
2954 void VK_Sprite::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
2955 VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
2956
2957 VkSubmitInfo submitInfo{};
2958 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2959 submitInfo.commandBufferCount = 1;
2960 submitInfo.pCommandBuffers = &commandBuffer;
2961
2962 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE));
2963 VK_CHECK_RESULT(vkQueueWaitIdle(graphicsQueue));
2964
2965 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
2966 }
2967
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;
2971
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;
2986
2987 try {
2988 VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &newImage));
2989
2990 VkMemoryRequirements memRequirements;
2991 vkGetImageMemoryRequirements(device, newImage, &memRequirements);
2992
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));
2998 VK_CHECK_RESULT(vkBindImageMemory(device, newImage, newMemory, 0));
2999 } catch (...) {
3000 if (newImage != VK_NULL_HANDLE) {
3001 vkDestroyImage(device, newImage, nullptr);
3002 }
3003 if (newMemory != VK_NULL_HANDLE) {
3004 vkFreeMemory(device, newMemory, nullptr);
3005 }
3006 throw;
3007 }
3008
3009 if (image != VK_NULL_HANDLE) {
3010 vkDestroyImage(device, image, nullptr);
3011 }
3012 if (imageMemory != VK_NULL_HANDLE) {
3013 vkFreeMemory(device, imageMemory, nullptr);
3014 }
3015 image = newImage;
3016 imageMemory = newMemory;
3017 }
3018
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;
3030
3031 VkImageView imageView;
3032 VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &imageView));
3033 return imageView;
3034 }
3035
3036 SDL_Surface *VK_Sprite::convertToRGBA(SDL_Surface *surface) {
3037 // SDL3: SDL_ConvertSurface handles format conversion including adding alpha
3038 SDL_Surface *converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
3039 return converted;
3040 }
3041
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);
3045
3046 if (requested.is_absolute() || requested.has_parent_path()) {
3047 candidates.push_back(requested);
3048 } else {
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);
3053 }
3054 candidates.push_back(std::filesystem::path("data") / requested);
3055 candidates.push_back(requested);
3056 }
3057
3058 std::ifstream file;
3059 for (const std::filesystem::path &candidate : candidates) {
3060 file.open(candidate, std::ios::ate | std::ios::binary);
3061 if (file.is_open()) {
3062 break;
3063 }
3064 file.clear();
3065 }
3066
3067 if (!file.is_open()) {
3068 throw mxvk::Exception("Failed to open shader file: " + filename);
3069 }
3070
3071 size_t fileSize = static_cast<size_t>(file.tellg());
3072 std::vector<char> buffer(fileSize);
3073 file.seekg(0);
3074 file.read(buffer.data(), fileSize);
3075 file.close();
3076
3077 return buffer;
3078 }
3079
3080} // namespace mxvk
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.
Definition mxvk.hpp:31
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.
Definition mxvk_png.cpp:89