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.hpp
Go to the documentation of this file.
1/**
2 * @file mxvk_sprite.hpp
3 * @brief Vulkan 2-D sprite renderer with optional custom shaders and instancing.
4 *
5 * VKSprite manages a Vulkan texture, a screen-space quad, and an optional
6 * custom graphics pipeline. It supports:
7 * - Loading images from PNG files or SDL_Surface objects.
8 * - Drawing at arbitrary positions, scales, and rotations.
9 * - GPU instancing for large batches of identical sprites.
10 * - Extended UBO with mouse state and four custom vec4 uniforms.
11 */
12#pragma once
13
14#include <volk/volk.h>
15
16#include <SDL3/SDL.h>
17
18#include "mxvk_exception.hpp"
19#include <array>
20#include <cstdlib>
21#include <cstring>
22#include <format>
23#include <fstream>
24#include <glm/glm.hpp>
25#include <iostream>
26#include <stdexcept>
27#include <string>
28#include <unordered_map>
29#include <vector>
30#ifdef MXVK_CUDA
31#include <cuda_runtime_api.h>
32#include <opencv2/core/cuda.hpp>
33#endif
34
35#ifndef VK_CHECK_RESULT
36#define VK_CHECK_RESULT(f) \
37 { \
38 VkResult res = (f); \
39 if (res != VK_SUCCESS) { \
40 throw mxvk::Exception(std::format("Fatal : VkResult is \"{}\" in {} at line {}", static_cast<int>(res), __FILE__, __LINE__)); \
41 } \
42 }
43#endif
44
45namespace mxvk {
46
47 /**
48 * @class VKSprite
49 * @brief Vulkan 2-D sprite with texture, custom shader, and instancing support.
50 *
51 * Allocates and manages all Vulkan resources required to render one sprite
52 * type: image, sampler, descriptor set, vertex/index buffers, and an
53 * optional custom pipeline. Multiple draw commands are batched into a
54 * queue and submitted together in renderSprites().
55 */
56 class VK_Sprite {
57 public:
58 static constexpr std::size_t MAX_CUSTOM_UNIFORMS = 64;
59
60 /**
61 * @brief Construct and record Vulkan context handles.
62 * @param device Logical device.
63 * @param physicalDevice Physical device.
64 * @param graphicsQueue Graphics queue.
65 * @param commandPool Command pool for staging operations.
66 */
67 VK_Sprite(VkDevice device, VkPhysicalDevice physicalDevice, VkQueue graphicsQueue, VkCommandPool commandPool);
68
69 /** @brief Destructor — frees all Vulkan resources. */
70 ~VK_Sprite();
71
72 VK_Sprite(const VK_Sprite &) = delete;
73 VK_Sprite &operator=(const VK_Sprite &) = delete;
74 VK_Sprite(VK_Sprite &&) = delete;
76
77 /**
78 * @brief Load sprite texture from a PNG file.
79 * @param pngPath Path to the PNG file.
80 * @param fragmentShaderPath Optional custom fragment shader (SPIR-V .spv).
81 */
82 void loadSprite(const std::string &pngPath, const std::string &fragmentShaderPath = "");
83
84 /**
85 * @brief Load sprite texture from an SDL_Surface.
86 * @param surface Source surface (not consumed).
87 * @param fragmentShaderPath Optional custom fragment shader.
88 */
89 void loadSprite(SDL_Surface *surface, const std::string &fragmentShaderPath = "");
90
91 /**
92 * @brief Create a blank (un-initialised) sprite texture.
93 * @param width Pixel width.
94 * @param height Pixel height.
95 * @param vertexShaderPath Optional vertex shader.
96 * @param fragmentShaderPath Optional fragment shader.
97 */
98 void createEmptySprite(int width, int height, const std::string &vertexShaderPath = "", const std::string &fragmentShaderPath = "");
99
100 /**
101 * @brief Create a blank RGBA16 UNORM sprite texture.
102 *
103 * This preserves transfer-encoded 10/12-bit video input without
104 * quantizing it to RGBA8. It does not change render-target precision.
105 */
106 void createEmptySpriteRgba16(int width, int height, const std::string &vertexShaderPath = "", const std::string &fragmentShaderPath = "");
107
108 /**
109 * @brief Queue a draw at the given pixel position.
110 * @param x Destination X.
111 * @param y Destination Y.
112 */
113 void drawSprite(int x, int y);
114
115 /**
116 * @brief Queue a scaled draw.
117 * @param x Destination X.
118 * @param y Destination Y.
119 * @param scaleX Horizontal scale factor.
120 * @param scaleY Vertical scale factor.
121 */
122 void drawSprite(int x, int y, float scaleX, float scaleY);
123
124 /**
125 * @brief Queue a scaled and rotated draw.
126 * @param x Destination X.
127 * @param y Destination Y.
128 * @param scaleX Horizontal scale.
129 * @param scaleY Vertical scale.
130 * @param rotation Clockwise rotation in degrees.
131 */
132 void drawSprite(int x, int y, float scaleX, float scaleY, float rotation);
133
134 /**
135 * @brief Queue a draw into an explicit destination rectangle.
136 * @param x,y,w,h Destination rectangle.
137 */
138 void drawSpriteRect(int x, int y, int w, int h);
139
140 /**
141 * @brief Replace the sprite texture from an SDL_Surface.
142 * @param surface New surface (not consumed).
143 */
144 void updateTexture(SDL_Surface *surface);
145
146 /**
147 * @brief Replace the sprite texture from a raw pixel buffer.
148 * @param pixels Pointer to RGBA data.
149 * @param width Buffer width.
150 * @param height Buffer height.
151 * @param pitch Row stride in bytes (0 = auto).
152 */
153 void updateTexture(const void *pixels, int width, int height, int pitch = 0);
154
155 /**
156 * @brief Replace an RGBA16 UNORM sprite from native-endian uint16 data.
157 * @param pixels Pointer to four uint16 components per pixel.
158 * @param width Buffer width.
159 * @param height Buffer height.
160 * @param pitch Row stride in bytes (0 = tightly packed).
161 */
162 void updateTextureRgba16(const uint16_t *pixels, int width, int height, int pitch = 0);
163
164 void setExternalTexture(VkImageView image_view, int width, int height);
166
167#ifdef MXVK_CUDA
168 /** @brief Replace the sprite texture directly from CUDA device memory. */
169 bool updateTextureCuda(const cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream);
170#endif
171
172 /**
173 * @brief Set up to four custom shader float parameters.
174 * @param p1,p2,p3,p4 Parameter values packed into a vec4.
175 */
176 void setShaderParams(float p1 = 0.0f, float p2 = 0.0f, float p3 = 0.0f, float p4 = 0.0f);
177
178 /**
179 * @brief Enable or disable the custom fragment shader effects.
180 * @param enabled @c true to enable effects.
181 */
182 void setEffectsEnabled(bool enabled) { effectsEnabled = enabled; }
183
184 /** @return @c true if shader effects are enabled. */
185 bool getEffectsEnabled() const { return effectsEnabled; }
186
187 /**
188 * @brief Record all queued draw commands into the given command buffer.
189 * @param cmdBuffer Active command buffer.
190 * @param pipelineLayout Pipeline layout for push constants/descriptors.
191 * @param screenWidth Current viewport width.
192 * @param screenHeight Current viewport height.
193 */
194 void renderSprites(VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout, uint32_t screenWidth, uint32_t screenHeight);
195
196 /**
197 * @brief Record texture barriers that must happen before dynamic rendering begins.
198 * @param cmdBuffer Command buffer currently being recorded outside a rendering instance.
199 */
200 void prepareForRendering(VkCommandBuffer cmdBuffer);
201
202 /** @brief Discard all pending draw commands without rendering. */
203 void clearQueue();
204
205 /** @return Sprite texture width in pixels. */
206 int getWidth() const { return spriteWidth; }
207 /** @return Sprite texture height in pixels. */
208 int getHeight() const { return spriteHeight; }
209
210 /**
211 * @brief Select the hardware filter used when scaling this sprite.
212 * @param filter VK_FILTER_NEAREST for sharp pixels or VK_FILTER_LINEAR for smoothing.
213 */
214 void setTextureFilter(VkFilter filter);
215
216 /** @brief Assign an external descriptor-set layout. */
217 void setDescriptorSetLayout(VkDescriptorSetLayout layout) { descriptorSetLayout = layout; }
218 /** @brief Assign the render pass used to build the custom pipeline. */
219 void setRenderPass(VkRenderPass rp) { renderPass = rp; }
220 /** @brief Assign dynamic-rendering color attachment format used to build pipelines. */
221 void setColorAttachmentFormat(VkFormat format) { colorAttachmentFormat = format; }
222 /** @brief Assign dynamic-rendering depth attachment format used to build pipelines. */
223 void setDepthAttachmentFormat(VkFormat format) { depthAttachmentFormat = format; }
224 /**
225 * @brief Rebind the command pool used for upload/staging operations.
226 *
227 * Any in-flight upload resources tied to the previous pool are released first.
228 */
229 void setCommandPool(VkCommandPool pool);
230 /** @brief Use the shared pipeline cache for custom/instanced pipeline creation. */
231 void setPipelineCache(VkPipelineCache cache) { pipelineCache = cache; }
232 /**
233 * @brief Release upload/staging resources tied to the current command pool.
234 *
235 * Call this before destroying or recreating the command pool.
236 */
238 /** @brief Override the vertex shader path (used when rebuilding the pipeline). */
239 void setVertexShaderPath(const std::string &path) { vertexShaderPath = path; }
240 /** @brief Replace the fragment shader path and rebuild the custom pipeline. */
241 void setFragmentShaderPath(const std::string &path);
242
243 /**
244 * @brief Build a compute image-effect pipeline for this sprite.
245 *
246 * Compute shaders use the extended sprite descriptor ABI: binding 0 is
247 * the sampled input image, binding 1 is SpriteExtended, optional
248 * history/audio textures remain at bindings 2-4, and binding 5 is a
249 * write-only storage image matching the active render precision.
250 */
251 void enableComputeShader(const std::string &path, uint32_t localSizeX, uint32_t localSizeY, uint32_t localSizeZ = 1);
252
253 /** @return Whether this sprite owns an executable compute pipeline. */
254 [[nodiscard]] bool hasComputePipeline() const { return computePipeline != VK_NULL_HANDLE; }
255
256 /** @brief Dispatch the compute effect between two full-frame images. */
257 void dispatchCompute(VkCommandBuffer cmdBuffer, VkImageView inputView, VkImageView outputView, uint32_t width, uint32_t height);
258
259 /** @return @c true if a custom pipeline has been built. */
260 bool hasOwnPipeline() const { return customPipeline != VK_NULL_HANDLE; }
261 /** @return The custom VkPipeline handle (may be VK_NULL_HANDLE). */
262 VkPipeline getPipeline() const { return customPipeline; }
263 /** @return The custom pipeline layout handle. */
264 VkPipelineLayout getPipelineLayout() const { return customPipelineLayout; }
265
266 /** @brief Destroy and recreate the custom graphics pipeline. */
267 void rebuildPipeline();
268 /** @brief Destroy and recreate the instanced graphics pipeline. */
270
271 VkSampler spriteSampler = VK_NULL_HANDLE; ///< Texture sampler.
272
273 /**
274 * @brief Enable GPU instancing for this sprite type.
275 * @param maxInstances Maximum simultaneous instances.
276 * @param instanceVertShaderPath Vertex shader supporting instancing.
277 * @param instanceFragShaderPath Fragment shader.
278 */
279 void enableInstancing(uint32_t maxInstances, const std::string &instanceVertShaderPath, const std::string &instanceFragShaderPath);
280
281 /** @return @c true if GPU instancing is active. */
282 bool isInstancingEnabled() const { return instancingEnabled; }
283
284 /** @brief Allocate and initialise the extended uniform buffer object. */
285 void enableExtendedUBO();
286
287 /** @return @c true if the extended UBO is active. */
288 bool isExtendedUBOEnabled() const { return extendedUBOEnabled; }
289
290 /**
291 * @brief Upload mouse state to the extended UBO.
292 * @param mx Mouse X (normalised or pixels).
293 * @param my Mouse Y.
294 * @param pressed Mouse button state.
295 * @param reserved Reserved channel.
296 */
297 void setMouseState(float mx, float my, float pressed, float reserved = 0.0f);
298
299 /** @brief Upload user uniform 0 to the extended UBO. @param x,y,z,w Components. */
300 void setUniform0(float x, float y, float z, float w);
301 /** @brief Upload user uniform 1 to the extended UBO. @param x,y,z,w Components. */
302 void setUniform1(float x, float y, float z, float w);
303 /** @brief Upload user uniform 2 to the extended UBO. @param x,y,z,w Components. */
304 void setUniform2(float x, float y, float z, float w);
305 /** @brief Upload user uniform 3 to the extended UBO. @param x,y,z,w Components. */
306 void setUniform3(float x, float y, float z, float w);
307
308 /**
309 * @brief Upload audio frequency-band energy to the extended UBO.
310 *
311 * The values are appended after the custom-uniform array to preserve
312 * the existing SpriteExtended prefix and custom-uniform offsets.
313 *
314 * @param low Energy below 300 Hz.
315 * @param mid Energy from 300 through 3000 Hz.
316 * @param high Energy above 3000 Hz.
317 * @param reserved Reserved channel.
318 */
319 void setAudioBands(float low, float mid, float high, float reserved = 0.0f);
320
321 /**
322 * @brief Upload ordered custom float values to the extended UBO.
323 *
324 * Custom shaders can append @c vec4 custom_uniforms[16] after @c u3 in
325 * their binding-1 SpriteExtended block. Value N is available at
326 * @c custom_uniforms[N/4][N%4]. Existing shaders that use only the
327 * original SpriteExtended prefix remain compatible.
328 *
329 * @param values Up to MAX_CUSTOM_UNIFORMS values. Unused slots are zeroed.
330 * @throws mxvk::Exception when too many values are supplied.
331 */
332 void setCustomUniforms(const std::vector<float> &values);
333
334 /**
335 * @brief Allocate a shader-readable RGBA history texture array.
336 *
337 * Enables extended descriptors and exposes the array as a combined image
338 * sampler at set 0, binding 2. Replaces any previously allocated history
339 * texture. The texture is initialized to transparent black.
340 *
341 * @param width Width of every history layer in pixels.
342 * @param height Height of every history layer in pixels.
343 * @param layers Number of layers in the circular history buffer.
344 * @throws mxvk::Exception when any dimension is zero.
345 */
346 void enableHistoryTexture(uint32_t width, uint32_t height, uint32_t layers);
347
348 /** @brief Allocate a shader-readable RGBA16F history texture array. */
349 void enableHistoryTextureRgba16Float(uint32_t width, uint32_t height, uint32_t layers);
350
351 /**
352 * @brief Bind another sprite's history array without taking ownership.
353 *
354 * The source sprite must outlive this sprite. This is intended for
355 * post-processing passes which read one shared input history ring.
356 * No image, memory, or image view is allocated or freed by this sprite.
357 *
358 * @param source Sprite which owns an enabled history texture.
359 * @throws mxvk::Exception when the source has no history texture or
360 * belongs to a different Vulkan device.
361 */
362 void shareHistoryTexture(const VK_Sprite &source);
363
364 /**
365 * @brief Upload one RGBA frame into the next history layer.
366 *
367 * The write head advances after a successful upload. Input dimensions
368 * must match those passed to enableHistoryTexture().
369 *
370 * @param pixels RGBA8 source pixels.
371 * @param width Source width in pixels.
372 * @param height Source height in pixels.
373 * @param pitch Source row stride in bytes, or zero for tightly packed data.
374 * @throws mxvk::Exception for null data, invalid dimensions, or an inactive cache.
375 */
376 void updateHistoryTexture(const void *pixels, int width, int height, int pitch = 0);
377
378 /** @brief Upload normalized RGBA16 pixels into an RGBA16F history layer. */
379 void updateHistoryTextureRgba16(const uint16_t *pixels, int width, int height, int pitch = 0);
380
381#ifdef MXVK_CUDA
382 /**
383 * @brief Upload a CUDA RGBA frame into the next history layer.
384 *
385 * Copies device-to-device into the Vulkan history array and advances
386 * the write head after a successful upload. Dimensions and format must
387 * match the active RGBA8 history texture.
388 *
389 * @param rgba CUDA RGBA8 source image.
390 * @param stream CUDA stream used for the asynchronous copy.
391 * @return @c true on success, or @c false for invalid input or when
392 * direct CUDA/Vulkan history interop is unavailable.
393 */
394 [[nodiscard]] bool updateHistoryTextureCuda(const cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream);
395#endif
396
397 /** @return The logical oldest-layer index for a circular history sampler. */
398 [[nodiscard]] uint32_t getHistoryHead() const { return historyHead; }
399
400 /** @return Number of allocated texture-history layers. */
401 [[nodiscard]] uint32_t getHistoryLayerCount() const { return historyLayers; }
402
403 /**
404 * @brief Allocate a shader-readable 1-D floating-point spectrum texture.
405 *
406 * Enables extended descriptors and exposes the texture as a combined
407 * image sampler at set 0, binding 3. The texture is initialized to zero.
408 *
409 * @param bins Number of R32_SFLOAT frequency bins.
410 * @throws mxvk::Exception when @p bins is zero.
411 */
412 void enableSpectrumTexture(uint32_t bins);
413
414 /**
415 * @brief Replace the current floating-point spectrum data.
416 * @param magnitudes Pointer to @p bins frequency magnitudes.
417 * @param bins Number of values; must match enableSpectrumTexture().
418 */
419 void updateSpectrumTexture(const float *magnitudes, uint32_t bins);
420
421 /** @return Number of allocated spectrum bins. */
422 [[nodiscard]] uint32_t getSpectrumBinCount() const { return spectrumBins; }
423
424 /**
425 * @brief Allocate a shader-readable FFT spectrum-history array.
426 *
427 * Enables extended descriptors and exposes the history as a combined
428 * image sampler at set 0, binding 4. The R32_SFLOAT 1-D array is
429 * initialized to zero and uses a circular write head.
430 *
431 * @param bins Number of frequency bins in every history layer.
432 * @param layers Requested number of history layers. The active GPU's
433 * maximum image-array-layer limit is applied automatically.
434 * @return Number of history layers actually allocated.
435 * @throws mxvk::Exception when either requested dimension is zero.
436 */
437 uint32_t enableSpectrumHistoryTexture(uint32_t bins, uint32_t layers);
438
439 /**
440 * @brief Upload one FFT spectrum into the next history layer.
441 *
442 * @param magnitudes Pointer to @p bins frequency magnitudes.
443 * @param bins Number of values; must match the configured history.
444 */
445 void updateSpectrumHistoryTexture(const float *magnitudes, uint32_t bins);
446
447 /** @return Physical array layer containing the newest FFT spectrum. */
448 [[nodiscard]] uint32_t getSpectrumHistoryHead() const { return spectrumHistoryHead; }
449
450 /** @return Number of allocated FFT spectrum-history layers. */
451 [[nodiscard]] uint32_t getSpectrumHistoryLayerCount() const { return spectrumHistoryLayers; }
452
453 private:
454 struct SpriteVertex {
455 float pos[2];
456 float texCoord[2];
457 };
458
459 struct SpriteDrawCmd {
460 float x, y, w, h;
461 float rotation;
462 glm::vec4 params;
463 };
464
465 VkDevice device = VK_NULL_HANDLE;
466 VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
467 VkQueue graphicsQueue = VK_NULL_HANDLE;
468 VkCommandPool commandPool = VK_NULL_HANDLE;
469 VkPipelineCache pipelineCache = VK_NULL_HANDLE;
470 VkImage spriteImage = VK_NULL_HANDLE;
471 VkDeviceMemory spriteImageMemory = VK_NULL_HANDLE;
472 VkImageView spriteImageView = VK_NULL_HANDLE;
473 VkFormat spriteImageFormat = VK_FORMAT_R8G8B8A8_UNORM;
474 uint32_t spriteBytesPerPixel = 4;
475 int spriteWidth = 0;
476 int spriteHeight = 0;
477 bool spriteLoaded = false;
478 bool externalTexture = false;
479 VkShaderModule fragmentShaderModule = VK_NULL_HANDLE;
480 VkShaderModule computeShaderModule = VK_NULL_HANDLE;
481 VkPipeline computePipeline = VK_NULL_HANDLE;
482 VkPipelineLayout computePipelineLayout = VK_NULL_HANDLE;
483 VkImageView computeOutputImageView = VK_NULL_HANDLE;
484 uint32_t computeLocalSizeX = 1;
485 uint32_t computeLocalSizeY = 1;
486 void createComputePipeline();
487 void destroyComputePipeline();
488 bool hasCustomShader = false;
489 glm::vec4 shaderParams = glm::vec4(0.0f);
490 bool effectsEnabled = true;
491 std::vector<SpriteDrawCmd> drawQueue;
492
493 VkPipeline customPipeline = VK_NULL_HANDLE;
494 VkPipelineLayout customPipelineLayout = VK_NULL_HANDLE;
495 VkRenderPass renderPass = VK_NULL_HANDLE;
496 VkFormat colorAttachmentFormat = VK_FORMAT_UNDEFINED;
497 VkFormat depthAttachmentFormat = VK_FORMAT_UNDEFINED;
498 std::string vertexShaderPath;
499 std::string fragmentShaderPath;
500 void createCustomPipeline();
501
502 VkBuffer quadVertexBuffer = VK_NULL_HANDLE;
503 VkDeviceMemory quadVertexBufferMemory = VK_NULL_HANDLE;
504 VkBuffer quadIndexBuffer = VK_NULL_HANDLE;
505 VkDeviceMemory quadIndexBufferMemory = VK_NULL_HANDLE;
506 bool quadBufferCreated = false;
507 void createQuadBuffer();
508
509 VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
510 std::vector<VkDescriptorPool> descriptorPools{};
511 VkDescriptorPool descriptorSetPool = VK_NULL_HANDLE;
512 uint32_t nextDescriptorPoolSets = 16;
513 VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
514 VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
515 std::unordered_map<VkImageView, VkDescriptorSet> externalDescriptorSets{};
516 VkFilter textureFilter = VK_FILTER_LINEAR;
517 void createDescriptorPool();
518 void destroyDescriptorPools();
519 void destroyTextureDescriptorPools();
520 VkDescriptorSet createDescriptorSet(VkImageView imageView);
521 void destroySpriteResources();
522
523 VkBuffer persistentStagingBuffer = VK_NULL_HANDLE;
524 VkDeviceMemory persistentStagingMemory = VK_NULL_HANDLE;
525 void *persistentStagingMapped = nullptr;
526 VkDeviceSize persistentStagingSize = 0;
527 VkFence uploadFence = VK_NULL_HANDLE;
528 VkCommandBuffer uploadCmdBuffer = VK_NULL_HANDLE;
529 bool stagingResourcesCreated = false;
530 [[nodiscard]] VkDeviceSize stagingAllocationSize(VkDeviceSize requiredSize) const;
531 void createStagingResources(VkDeviceSize size);
532 void destroyStagingResources();
533 void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory);
534 uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties);
535 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height, uint32_t baseArrayLayer = 0, uint32_t layerCount = 1);
536 VkCommandBuffer beginSingleTimeCommands();
537 void endSingleTimeCommands(VkCommandBuffer commandBuffer);
538 void transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t baseArrayLayer = 0, uint32_t layerCount = 1);
539 void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &imageMemory, uint32_t arrayLayers = 1, VkImageType imageType = VK_IMAGE_TYPE_2D);
540 VkImageView createImageView(VkImage image, VkFormat format, VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D, uint32_t layerCount = 1);
541 void createSampler();
542 SDL_Surface *convertToRGBA(SDL_Surface *surface);
543 void createSpriteTexture(SDL_Surface *surface);
544 void createEmptySpriteWithFormat(int width, int height, VkFormat format, uint32_t bytesPerPixel, const std::string &vertexShaderPath, const std::string &fragmentShaderPath);
545 void updateSpriteTexture(const void *pixels, uint32_t width, uint32_t height);
546#ifdef MXVK_CUDA
547 void destroyCudaInterop();
548 void destroyCudaHistoryInterop();
549 bool ensureCudaInterop();
550 bool ensureCudaHistoryInterop();
551 bool transitionCudaImageForWrite();
552 bool transitionCudaImageForShaderRead();
553 void transitionCudaHistoryLayer(VkImageLayout oldLayout, VkImageLayout newLayout, VkAccessFlags sourceAccess, VkAccessFlags destinationAccess, VkPipelineStageFlags sourceStage, VkPipelineStageFlags destinationStage);
554 bool updateTextureCudaHost(const void *pixels, uint32_t width, uint32_t height, uint32_t pitch);
555 void recordCudaReadyBarrier(VkCommandBuffer cmdBuffer);
556 void createCudaExportableImage(uint32_t width, uint32_t height, uint32_t arrayLayers, VkImage &image, VkDeviceMemory &imageMemory, VkDeviceSize &exportMemorySize);
557 VkDeviceSize cudaExportMemorySize = 0;
558 cudaExternalMemory_t cudaExternalMemory = nullptr;
559 cudaMipmappedArray_t cudaMipmappedArray = nullptr;
560 cudaArray_t cudaArray = nullptr;
561 bool cudaInteropEnabled = false;
562 bool cudaInteropUnavailableLogged = false;
563 bool cudaUploadLogged = false;
564 bool cudaWriteTransitionLogged = false;
565 bool cudaSampleBarrierLogged = false;
566 bool cudaImageNeedsShaderBarrier = false;
567 VkImageLayout cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
568 VkDeviceSize cudaHistoryExportMemorySize = 0;
569 cudaExternalMemory_t cudaHistoryExternalMemory = nullptr;
570 cudaMipmappedArray_t cudaHistoryMipmappedArray = nullptr;
571 cudaArray_t cudaHistoryArray = nullptr;
572 bool cudaHistoryInteropEnabled = false;
573 bool cudaHistoryInteropUnavailableLogged = false;
574 bool cudaHistoryUploadLogged = false;
575#endif
576 std::vector<char> readShaderFile(const std::string &filename);
577
578 struct SpriteExtendedUBO {
579 glm::vec4 mouse;
580 glm::vec4 u0;
581 glm::vec4 u1;
582 glm::vec4 u2;
583 glm::vec4 u3;
584 std::array<glm::vec4, MAX_CUSTOM_UNIFORMS / 4> custom_uniforms;
585 glm::vec4 audio_bands;
586 glm::vec4 audio_history;
587 };
588 bool extendedUBOEnabled = false;
589 SpriteExtendedUBO extendedUBOData{};
590 VkBuffer extendedUBOBuffer = VK_NULL_HANDLE;
591 VkDeviceMemory extendedUBOMemory = VK_NULL_HANDLE;
592 void *extendedUBOMapped = nullptr;
593 VkDescriptorSetLayout extendedDescriptorSetLayout = VK_NULL_HANDLE;
594 VkDescriptorPool extendedDescriptorPool = VK_NULL_HANDLE;
595 VkDescriptorSet extendedDescriptorSet = VK_NULL_HANDLE;
596 bool ownExtendedDescriptorSetLayout = false;
597 void createExtendedUBO();
598 void updateExtendedUBO();
599 void createExtendedDescriptorSetLayout();
600 void createExtendedDescriptorSet();
601 void destroyExtendedUBO();
602
603 bool historyTextureEnabled = false;
604 bool historyTextureShared = false;
605 VkImage historyImage = VK_NULL_HANDLE;
606 VkDeviceMemory historyImageMemory = VK_NULL_HANDLE;
607 VkImageView historyImageView = VK_NULL_HANDLE;
608 VkFormat historyImageFormat = VK_FORMAT_R8G8B8A8_UNORM;
609 uint32_t historyWidth = 0;
610 uint32_t historyHeight = 0;
611 uint32_t historyLayers = 0;
612 uint32_t historyHead = 0;
613 void destroyHistoryTexture();
614 void enableHistoryTextureWithFormat(uint32_t width, uint32_t height, uint32_t layers, VkFormat format);
615 void uploadHistoryTextureBytes(const void *pixels, int width, int height, int pitch, int bytesPerPixel);
616
617 bool spectrumTextureEnabled = false;
618 VkImage spectrumImage = VK_NULL_HANDLE;
619 VkDeviceMemory spectrumImageMemory = VK_NULL_HANDLE;
620 VkImageView spectrumImageView = VK_NULL_HANDLE;
621 uint32_t spectrumBins = 0;
622 void destroySpectrumTexture();
623
624 bool spectrumHistoryTextureEnabled = false;
625 VkImage spectrumHistoryImage = VK_NULL_HANDLE;
626 VkDeviceMemory spectrumHistoryImageMemory = VK_NULL_HANDLE;
627 VkImageView spectrumHistoryImageView = VK_NULL_HANDLE;
628 uint32_t spectrumHistoryBins = 0;
629 uint32_t spectrumHistoryLayers = 0;
630 uint32_t spectrumHistoryHead = 0;
631 uint32_t spectrumHistoryWriteIndex = 0;
632 void destroySpectrumHistoryTexture();
633 void recreateExtendedDescriptorLayout();
634
635 struct SpriteInstanceData {
636 float posX, posY, sizeW, sizeH;
637 float params[4];
638 };
639 VkBuffer instanceBuffer = VK_NULL_HANDLE;
640 VkDeviceMemory instanceBufferMemory = VK_NULL_HANDLE;
641 void *instanceBufferMapped = nullptr;
642 uint32_t instanceBufferCapacity = 0;
643 bool instancingEnabled = false;
644 VkPipeline instancedPipeline = VK_NULL_HANDLE;
645 VkPipelineLayout instancedPipelineLayout = VK_NULL_HANDLE;
646 std::string instanceVertPath;
647 std::string instanceFragPath;
648 void createInstancedPipeline(const std::string &vertPath, const std::string &fragPath);
649 void ensureInstanceBuffer(uint32_t count);
650 void destroyInstanceResources();
651 };
652
653} // 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.
VkPipelineLayout getPipelineLayout() const
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).
uint32_t getSpectrumHistoryLayerCount() const
static constexpr std::size_t MAX_CUSTOM_UNIFORMS
bool isInstancingEnabled() const
void releaseUploadResources()
Release upload/staging resources tied to the current command pool.
uint32_t getSpectrumBinCount() const
void setUniform1(float x, float y, float z, float w)
Upload user uniform 1 to the extended UBO.
int getHeight() const
bool getEffectsEnabled() const
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.
uint32_t getHistoryHead() const
void setDepthAttachmentFormat(VkFormat format)
Assign dynamic-rendering depth attachment format used to build pipelines.
VK_Sprite(VK_Sprite &&)=delete
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.
VK_Sprite & operator=(VK_Sprite &&)=delete
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 setDescriptorSetLayout(VkDescriptorSetLayout layout)
Assign an external descriptor-set layout.
bool isExtendedUBOEnabled() const
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.
bool hasOwnPipeline() const
void rebuildInstancedPipeline()
Destroy and recreate the instanced graphics pipeline.
int getWidth() const
uint32_t getHistoryLayerCount() const
void setColorAttachmentFormat(VkFormat format)
Assign dynamic-rendering color attachment format used to build pipelines.
void setRenderPass(VkRenderPass rp)
Assign the render pass used to build the custom 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.
VkPipeline getPipeline() const
uint32_t enableSpectrumHistoryTexture(uint32_t bins, uint32_t layers)
Allocate a shader-readable FFT spectrum-history array.
uint32_t getSpectrumHistoryHead() const
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 setEffectsEnabled(bool enabled)
Enable or disable the custom fragment shader effects.
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.
bool hasComputePipeline() const
void updateHistoryTexture(const void *pixels, int width, int height, int pitch=0)
Upload one RGBA frame into the next history layer.
void setPipelineCache(VkPipelineCache cache)
Use the shared pipeline cache for custom/instanced pipeline creation.
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.
VK_Sprite & operator=(const VK_Sprite &)=delete
VK_Sprite(const VK_Sprite &)=delete
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.
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31