MXVK Vulkan Framework 0.33.1
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk::VK_Sprite Class Reference

#include <mxvk/include/mxvk/mxvk_sprite.hpp>

Public Member Functions

void clearExternalTextureDescriptors ()
void clearQueue ()
 Discard all pending draw commands without rendering.
void createEmptySprite (int width, int height, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
 Create a blank (un-initialised) sprite texture.
void dispatchCompute (VkCommandBuffer cmdBuffer, VkImageView inputView, VkImageView outputView, uint32_t width, uint32_t height)
 Dispatch the compute effect between two full-frame images.
void drawSprite (int x, int y)
 Queue a draw at the given pixel position.
void drawSprite (int x, int y, float scaleX, float scaleY)
 Queue a scaled draw.
void drawSprite (int x, int y, float scaleX, float scaleY, float rotation)
 Queue a scaled and rotated draw.
void drawSpriteRect (int x, int y, int w, int h)
 Queue a draw into an explicit destination rectangle.
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 enableExtendedUBO ()
 Allocate and initialise the extended uniform buffer object.
void enableHistoryTexture (uint32_t width, uint32_t height, uint32_t layers)
 Allocate a shader-readable RGBA history texture array.
void enableInstancing (uint32_t maxInstances, const std::string &instanceVertShaderPath, const std::string &instanceFragShaderPath)
 Enable GPU instancing for this sprite type.
uint32_t enableSpectrumHistoryTexture (uint32_t bins, uint32_t layers)
 Allocate a shader-readable FFT spectrum-history array.
void enableSpectrumTexture (uint32_t bins)
 Allocate a shader-readable 1-D floating-point spectrum texture.
bool getEffectsEnabled () const
int getHeight () const
uint32_t getHistoryHead () const
uint32_t getHistoryLayerCount () const
VkPipeline getPipeline () const
VkPipelineLayout getPipelineLayout () const
uint32_t getSpectrumBinCount () const
uint32_t getSpectrumHistoryHead () const
uint32_t getSpectrumHistoryLayerCount () const
int getWidth () const
bool hasComputePipeline () const
bool hasOwnPipeline () const
bool isExtendedUBOEnabled () const
bool isInstancingEnabled () const
void loadSprite (const std::string &pngPath, const std::string &fragmentShaderPath="")
 Load sprite texture from a PNG file.
void loadSprite (SDL_Surface *surface, const std::string &fragmentShaderPath="")
 Load sprite texture from an SDL_Surface.
VK_Spriteoperator= (const VK_Sprite &)=delete
VK_Spriteoperator= (VK_Sprite &&)=delete
void prepareForRendering (VkCommandBuffer cmdBuffer)
 Record texture barriers that must happen before dynamic rendering begins.
void rebuildInstancedPipeline ()
 Destroy and recreate the instanced graphics pipeline.
void rebuildPipeline ()
 Destroy and recreate the custom graphics pipeline.
void releaseUploadResources ()
 Release upload/staging resources tied to the current command pool.
void renderSprites (VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout, uint32_t screenWidth, uint32_t screenHeight)
 Record all queued draw commands into the given command buffer.
void setAudioBands (float low, float mid, float high, float reserved=0.0f)
 Upload audio frequency-band energy to the extended UBO.
void setColorAttachmentFormat (VkFormat format)
 Assign dynamic-rendering color attachment format used to build pipelines.
void setCommandPool (VkCommandPool pool)
 Rebind the command pool used for upload/staging operations.
void setCustomUniforms (const std::vector< float > &values)
 Upload ordered custom float values to the extended UBO.
void setDepthAttachmentFormat (VkFormat format)
 Assign dynamic-rendering depth attachment format used to build pipelines.
void setDescriptorSetLayout (VkDescriptorSetLayout layout)
 Assign an external descriptor-set layout.
void setEffectsEnabled (bool enabled)
 Enable or disable the custom fragment shader effects.
void setExternalTexture (VkImageView image_view, int width, int height)
void setFragmentShaderPath (const std::string &path)
 Replace the fragment shader path and rebuild the custom pipeline.
void setMouseState (float mx, float my, float pressed, float reserved=0.0f)
 Upload mouse state to the extended UBO.
void setPipelineCache (VkPipelineCache cache)
 Use the shared pipeline cache for custom/instanced pipeline creation.
void setRenderPass (VkRenderPass rp)
 Assign the render pass used to build the custom pipeline.
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 setTextureFilter (VkFilter filter)
 Select the hardware filter used when scaling this sprite.
void setUniform0 (float x, float y, float z, float w)
 Upload user uniform 0 to the extended UBO.
void setUniform1 (float x, float y, float z, float w)
 Upload user uniform 1 to the extended UBO.
void setUniform2 (float x, float y, float z, float w)
 Upload user uniform 2 to the extended UBO.
void setUniform3 (float x, float y, float z, float w)
 Upload user uniform 3 to the extended UBO.
void setVertexShaderPath (const std::string &path)
 Override the vertex shader path (used when rebuilding the pipeline).
void shareHistoryTexture (const VK_Sprite &source)
 Bind another sprite's history array without taking ownership.
void updateHistoryTexture (const void *pixels, int width, int height, int pitch=0)
 Upload one RGBA frame into the next history layer.
void updateSpectrumHistoryTexture (const float *magnitudes, uint32_t bins)
 Upload one FFT spectrum into the next history layer.
void updateSpectrumTexture (const float *magnitudes, uint32_t bins)
 Replace the current floating-point spectrum data.
void updateTexture (const void *pixels, int width, int height, int pitch=0)
 Replace the sprite texture from a raw pixel buffer.
void updateTexture (SDL_Surface *surface)
 Replace the sprite texture from an SDL_Surface.
 VK_Sprite (const VK_Sprite &)=delete
 VK_Sprite (VK_Sprite &&)=delete
 VK_Sprite (VkDevice device, VkPhysicalDevice physicalDevice, VkQueue graphicsQueue, VkCommandPool commandPool)
 Construct and record Vulkan context handles.
 ~VK_Sprite ()
 Destructor — frees all Vulkan resources.

Public Attributes

VkSampler spriteSampler = VK_NULL_HANDLE
 Texture sampler.

Static Public Attributes

static constexpr std::size_t MAX_CUSTOM_UNIFORMS = 64

Detailed Description

Definition at line 56 of file mxvk_sprite.hpp.

Constructor & Destructor Documentation

◆ VK_Sprite() [1/3]

mxvk::VK_Sprite::VK_Sprite ( VkDevice device,
VkPhysicalDevice physicalDevice,
VkQueue graphicsQueue,
VkCommandPool commandPool )

Construct and record Vulkan context handles.

Parameters
deviceLogical device.
physicalDevicePhysical device.
graphicsQueueGraphics queue.
commandPoolCommand pool for staging operations.

Definition at line 18 of file mxvk_sprite.cpp.

19 : device(dev), physicalDevice(physDev), graphicsQueue(gQueue), commandPool(cmdPool) {
20 std::cout << "mxvk: Created Sprite\n";
21 }

◆ ~VK_Sprite()

mxvk::VK_Sprite::~VK_Sprite ( )

Destructor — frees all Vulkan resources.

Definition at line 58 of file mxvk_sprite.cpp.

58 {
59 vkDeviceWaitIdle(device);
60 drawQueue.clear();
61
62 destroyStagingResources();
63#ifdef MXVK_CUDA
64 destroyCudaInterop();
65#endif
66 if (quadVertexBuffer != VK_NULL_HANDLE) {
67 std::cout << "vk: destroying sprite quad vertex buffer\n";
68 vkDestroyBuffer(device, quadVertexBuffer, nullptr);
69 vkFreeMemory(device, quadVertexBufferMemory, nullptr);
70 }
71 if (quadIndexBuffer != VK_NULL_HANDLE) {
72 std::cout << "vk: destroying sprite quad index buffer\n";
73 vkDestroyBuffer(device, quadIndexBuffer, nullptr);
74 vkFreeMemory(device, quadIndexBufferMemory, nullptr);
75 }
76
77 destroyTextureDescriptorPools();
78
79 if (spriteSampler != VK_NULL_HANDLE) {
80 std::cout << "vk: destroying sprite sampler\n";
81 vkDestroySampler(device, spriteSampler, nullptr);
82 }
83
84 if (!externalTexture && spriteImageView != VK_NULL_HANDLE) {
85 std::cout << "vk: destroying sprite image view\n";
86 vkDestroyImageView(device, spriteImageView, nullptr);
87 }
88
89 if (!externalTexture && spriteImage != VK_NULL_HANDLE) {
90 std::cout << "vk: destroying sprite image\n";
91 vkDestroyImage(device, spriteImage, nullptr);
92 std::cout << "vk: freeing sprite image memory\n";
93 vkFreeMemory(device, spriteImageMemory, nullptr);
94 }
95
96 if (fragmentShaderModule != VK_NULL_HANDLE) {
97 vkDestroyShaderModule(device, fragmentShaderModule, nullptr);
98 }
99
100 if (customPipeline != VK_NULL_HANDLE) {
101 std::cout << "vk: destroying sprite custom pipeline\n";
102 vkDestroyPipeline(device, customPipeline, nullptr);
103 }
104
105 if (customPipelineLayout != VK_NULL_HANDLE) {
106 std::cout << "vk: destroying sprite custom pipeline layout\n";
107 vkDestroyPipelineLayout(device, customPipelineLayout, nullptr);
108 }
109
110 destroyComputePipeline();
111 if (computeShaderModule != VK_NULL_HANDLE) {
112 vkDestroyShaderModule(device, computeShaderModule, nullptr);
113 computeShaderModule = VK_NULL_HANDLE;
114 }
115
116 destroyExtendedUBO();
117 destroyHistoryTexture();
118 destroySpectrumTexture();
119 destroySpectrumHistoryTexture();
120 destroyInstanceResources();
121 }
VkSampler spriteSampler
Texture sampler.

◆ VK_Sprite() [2/3]

mxvk::VK_Sprite::VK_Sprite ( const VK_Sprite & )
delete

◆ VK_Sprite() [3/3]

mxvk::VK_Sprite::VK_Sprite ( VK_Sprite && )
delete

Member Function Documentation

◆ clearExternalTextureDescriptors()

void mxvk::VK_Sprite::clearExternalTextureDescriptors ( )

Definition at line 2762 of file mxvk_sprite.cpp.

2762 {
2763 if (!externalTexture && externalDescriptorSets.empty()) {
2764 return;
2765 }
2766 destroyTextureDescriptorPools();
2767 }

◆ clearQueue()

void mxvk::VK_Sprite::clearQueue ( )

Discard all pending draw commands without rendering.

Definition at line 2868 of file mxvk_sprite.cpp.

2868 {
2869 drawQueue.clear();
2870 }

◆ createEmptySprite()

void mxvk::VK_Sprite::createEmptySprite ( int width,
int height,
const std::string & vertexShaderPath = "",
const std::string & fragmentShaderPath = "" )

Create a blank (un-initialised) sprite texture.

Parameters
widthPixel width.
heightPixel height.
vertexShaderPathOptional vertex shader.
fragmentShaderPathOptional fragment shader.

Definition at line 1739 of file mxvk_sprite.cpp.

1739 {
1740 if (width <= 0 || height <= 0) {
1741 throw mxvk::Exception("VKSprite::createEmptySprite invalid dimensions");
1742 }
1743 if (spriteLoaded || spriteImage != VK_NULL_HANDLE || fragmentShaderModule != VK_NULL_HANDLE) {
1744 destroySpriteResources();
1745 }
1746 spriteWidth = width;
1747 spriteHeight = height;
1748
1749 if (!vertexShaderPath.empty()) {
1750 setVertexShaderPath(vertexShaderPath);
1751 }
1752
1753#ifdef MXVK_CUDA
1754 try {
1755 createCudaExportableImage(width, height, 1, spriteImage,
1756 spriteImageMemory,
1757 cudaExportMemorySize);
1758 cudaInteropUnavailableLogged = false;
1759 } catch (const std::exception &ex) {
1760 std::cout << std::format("mxvk: CUDA exportable sprite image unavailable: {}; using standard Vulkan image\n", ex.what());
1761 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
1762 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1763 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
1764 }
1765#else
1766 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
1767 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1768 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spriteImage, spriteImageMemory);
1769#endif
1770
1771 VkBuffer stagingBuffer = VK_NULL_HANDLE;
1772 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
1773 VkDeviceSize imageSize = static_cast<VkDeviceSize>(width) * height * 4;
1774
1775 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
1776 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1777 stagingBuffer, stagingMemory);
1778
1779 void *data;
1780 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
1781 memset(data, 0, imageSize);
1782 vkUnmapMemory(device, stagingMemory);
1783
1784 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
1785 copyBufferToImage(stagingBuffer, spriteImage, width, height);
1786 transitionImageLayout(spriteImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
1787#ifdef MXVK_CUDA
1788 cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1789#endif
1790
1791 vkDestroyBuffer(device, stagingBuffer, nullptr);
1792 vkFreeMemory(device, stagingMemory, nullptr);
1793
1794 spriteImageView = createImageView(spriteImage, VK_FORMAT_R8G8B8A8_UNORM);
1795 createSampler();
1796 createQuadBuffer();
1797 createDescriptorPool();
1798
1799 createStagingResources(imageSize);
1800
1801 if (!fragmentShaderPath.empty()) {
1802 auto shaderCode = readShaderFile(fragmentShaderPath);
1803 fragmentShaderModule = mxvk::create_shader_module(device, shaderCode);
1804 hasCustomShader = true;
1805 this->fragmentShaderPath = fragmentShaderPath;
1806
1807 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
1808 createCustomPipeline();
1809 }
1810 }
1811
1812 spriteLoaded = true;
1813 std::cout << std::format("mxvk: Created empty sprite: {}x{}\n", spriteWidth, spriteHeight);
1814 }
void setVertexShaderPath(const std::string &path)
Override the vertex shader path (used when rebuilding the pipeline).
#define VK_CHECK_RESULT(f)
VkShaderModule create_shader_module(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.

◆ dispatchCompute()

void mxvk::VK_Sprite::dispatchCompute ( VkCommandBuffer cmdBuffer,
VkImageView inputView,
VkImageView outputView,
uint32_t width,
uint32_t height )

Dispatch the compute effect between two full-frame images.

Definition at line 1617 of file mxvk_sprite.cpp.

1620 {
1621 if (computePipeline == VK_NULL_HANDLE ||
1622 computePipelineLayout == VK_NULL_HANDLE || inputView == VK_NULL_HANDLE ||
1623 outputView == VK_NULL_HANDLE || width == 0 || height == 0) {
1624 throw mxvk::Exception(
1625 "VKSprite::dispatchCompute received an incomplete compute pass");
1626 }
1627
1628 setExternalTexture(inputView, static_cast<int>(width),
1629 static_cast<int>(height));
1630 if (computeOutputImageView != outputView) {
1631 if (extendedDescriptorPool != VK_NULL_HANDLE) {
1632 vkDeviceWaitIdle(device);
1633 vkDestroyDescriptorPool(device, extendedDescriptorPool, nullptr);
1634 extendedDescriptorPool = VK_NULL_HANDLE;
1635 extendedDescriptorSet = VK_NULL_HANDLE;
1636 }
1637 computeOutputImageView = outputView;
1638 }
1639 updateExtendedUBO();
1640 if (extendedDescriptorSet == VK_NULL_HANDLE) {
1641 createExtendedDescriptorSet();
1642 }
1643 if (extendedDescriptorSet == VK_NULL_HANDLE) {
1644 throw mxvk::Exception(
1645 "VKSprite::dispatchCompute could not create its descriptor set");
1646 }
1647
1648 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE,
1649 computePipeline);
1650 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE,
1651 computePipelineLayout, 0, 1,
1652 &extendedDescriptorSet, 0, nullptr);
1653 vkCmdDispatch(cmdBuffer,
1654 (width + computeLocalSizeX - 1U) / computeLocalSizeX,
1655 (height + computeLocalSizeY - 1U) / computeLocalSizeY,
1656 1U);
1657 }
void setExternalTexture(VkImageView image_view, int width, int height)

◆ drawSprite() [1/3]

void mxvk::VK_Sprite::drawSprite ( int x,
int y )

Queue a draw at the given pixel position.

Parameters
xDestination X.
yDestination Y.

Definition at line 2691 of file mxvk_sprite.cpp.

2691 {
2692 drawSpriteRect(x, y, spriteWidth, spriteHeight);
2693 }
void drawSpriteRect(int x, int y, int w, int h)
Queue a draw into an explicit destination rectangle.

◆ drawSprite() [2/3]

void mxvk::VK_Sprite::drawSprite ( int x,
int y,
float scaleX,
float scaleY )

Queue a scaled draw.

Parameters
xDestination X.
yDestination Y.
scaleXHorizontal scale factor.
scaleYVertical scale factor.

Definition at line 2695 of file mxvk_sprite.cpp.

2695 {
2696 drawSpriteRect(x, y, static_cast<int>(spriteWidth * scaleX), static_cast<int>(spriteHeight * scaleY));
2697 }

◆ drawSprite() [3/3]

void mxvk::VK_Sprite::drawSprite ( int x,
int y,
float scaleX,
float scaleY,
float rotation )

Queue a scaled and rotated draw.

Parameters
xDestination X.
yDestination Y.
scaleXHorizontal scale.
scaleYVertical scale.
rotationClockwise rotation in degrees.

Definition at line 2699 of file mxvk_sprite.cpp.

2699 {
2700 if (!spriteLoaded) {
2701 throw mxvk::Exception("VKSprite::drawSprite called before sprite was loaded");
2702 }
2703
2704 drawQueue.push_back({static_cast<float>(x), static_cast<float>(y),
2705 static_cast<float>(static_cast<int>(spriteWidth * scaleX)),
2706 static_cast<float>(static_cast<int>(spriteHeight * scaleY)),
2707 rotation, shaderParams});
2708 }

◆ drawSpriteRect()

void mxvk::VK_Sprite::drawSpriteRect ( int x,
int y,
int w,
int h )

Queue a draw into an explicit destination rectangle.

Parameters
x,y,w,hDestination rectangle.

Definition at line 2710 of file mxvk_sprite.cpp.

2710 {
2711 if (!spriteLoaded) {
2712 throw mxvk::Exception("VKSprite::drawSpriteRect called before sprite was loaded");
2713 }
2714
2715 drawQueue.push_back({static_cast<float>(x), static_cast<float>(y),
2716 static_cast<float>(w), static_cast<float>(h), 0.0f, shaderParams});
2717 }

◆ enableComputeShader()

void mxvk::VK_Sprite::enableComputeShader ( const std::string & path,
uint32_t localSizeX,
uint32_t localSizeY,
uint32_t localSizeZ = 1 )

Build a compute image-effect pipeline for this sprite.

Compute shaders use the extended sprite descriptor ABI: binding 0 is the sampled input image, binding 1 is SpriteExtended, optional history/audio textures remain at bindings 2-4, and binding 5 is a write-only RGBA8 storage image.

Definition at line 1588 of file mxvk_sprite.cpp.

1591 {
1592 if (path.empty() || localSizeX == 0 || localSizeY == 0 ||
1593 localSizeZ == 0) {
1594 throw mxvk::Exception(
1595 "VKSprite::enableComputeShader requires a shader and positive local size");
1596 }
1597
1598 vkDeviceWaitIdle(device);
1599 destroyComputePipeline();
1600 if (computeShaderModule != VK_NULL_HANDLE) {
1601 vkDestroyShaderModule(device, computeShaderModule, nullptr);
1602 computeShaderModule = VK_NULL_HANDLE;
1603 }
1604 computeShaderModule =
1605 mxvk::create_shader_module(device, readShaderFile(path));
1606 computeLocalSizeX = localSizeX;
1607 computeLocalSizeY = localSizeY;
1608
1609 if (!extendedUBOEnabled) {
1611 createComputePipeline();
1612 } else {
1613 recreateExtendedDescriptorLayout();
1614 }
1615 }
void enableExtendedUBO()
Allocate and initialise the extended uniform buffer object.

◆ enableExtendedUBO()

void mxvk::VK_Sprite::enableExtendedUBO ( )

Allocate and initialise the extended uniform buffer object.

Definition at line 176 of file mxvk_sprite.cpp.

176 {
177 if (extendedUBOEnabled)
178 return;
179 extendedUBOEnabled = true;
180 createExtendedUBO();
181 createExtendedDescriptorSetLayout();
183 }
void rebuildPipeline()
Destroy and recreate the custom graphics pipeline.

◆ enableHistoryTexture()

void mxvk::VK_Sprite::enableHistoryTexture ( uint32_t width,
uint32_t height,
uint32_t layers )

Allocate a shader-readable RGBA history texture array.

Enables extended descriptors and exposes the array as a combined image sampler at set 0, binding 2. Replaces any previously allocated history texture. The texture is initialized to transparent black.

Parameters
widthWidth of every history layer in pixels.
heightHeight of every history layer in pixels.
layersNumber of layers in the circular history buffer.
Exceptions
mxvk::Exceptionwhen any dimension is zero.

Definition at line 222 of file mxvk_sprite.cpp.

222 {
223 if (width == 0 || height == 0 || layers == 0) {
224 throw mxvk::Exception("VKSprite::enableHistoryTexture requires positive dimensions and layer count");
225 }
226
227 if (historyTextureEnabled && historyWidth == width && historyHeight == height && historyLayers == layers) {
228 return;
229 }
230
231 if (!extendedUBOEnabled) {
233 }
234
235 vkDeviceWaitIdle(device);
236 destroyHistoryTexture();
237
238 historyWidth = width;
239 historyHeight = height;
240 historyLayers = layers;
241 historyHead = 0;
242#ifdef MXVK_CUDA
243 try {
244 createCudaExportableImage(width, height, layers, historyImage,
245 historyImageMemory,
246 cudaHistoryExportMemorySize);
247 cudaHistoryInteropUnavailableLogged = false;
248 } catch (const std::exception &exception) {
249 std::cout << std::format(
250 "mxvk: CUDA exportable history image unavailable: {}; using "
251 "CPU staging uploads\n",
252 exception.what());
253 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM,
254 VK_IMAGE_TILING_OPTIMAL,
255 VK_IMAGE_USAGE_TRANSFER_DST_BIT |
256 VK_IMAGE_USAGE_SAMPLED_BIT,
257 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, historyImage,
258 historyImageMemory, layers);
259 }
260#else
261 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM,
262 VK_IMAGE_TILING_OPTIMAL,
263 VK_IMAGE_USAGE_TRANSFER_DST_BIT |
264 VK_IMAGE_USAGE_SAMPLED_BIT,
265 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, historyImage,
266 historyImageMemory, layers);
267#endif
268
269 const VkDeviceSize layerSize = static_cast<VkDeviceSize>(width) * height * 4;
270 const VkDeviceSize imageSize = layerSize * layers;
271 VkBuffer stagingBuffer = VK_NULL_HANDLE;
272 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
273 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
274 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
275 stagingBuffer, stagingMemory);
276
277 void *data = nullptr;
278 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
279 memset(data, 0, static_cast<std::size_t>(imageSize));
280 vkUnmapMemory(device, stagingMemory);
281
282 transitionImageLayout(historyImage, VK_IMAGE_LAYOUT_UNDEFINED,
283 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, layers);
284
285 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
286 std::vector<VkBufferImageCopy> regions(layers);
287 for (uint32_t layer = 0; layer < layers; ++layer) {
288 VkBufferImageCopy &region = regions[layer];
289 region.bufferOffset = layerSize * layer;
290 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
291 region.imageSubresource.mipLevel = 0;
292 region.imageSubresource.baseArrayLayer = layer;
293 region.imageSubresource.layerCount = 1;
294 region.imageExtent = {width, height, 1};
295 }
296 vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, historyImage,
297 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
298 static_cast<uint32_t>(regions.size()), regions.data());
299 endSingleTimeCommands(commandBuffer);
300
301 transitionImageLayout(historyImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
302 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0, layers);
303 vkDestroyBuffer(device, stagingBuffer, nullptr);
304 vkFreeMemory(device, stagingMemory, nullptr);
305
306 historyImageView = createImageView(historyImage, VK_FORMAT_R8G8B8A8_UNORM,
307 VK_IMAGE_VIEW_TYPE_2D_ARRAY, layers);
308 historyTextureEnabled = true;
309 historyTextureShared = false;
310 recreateExtendedDescriptorLayout();
311 }

◆ enableInstancing()

void mxvk::VK_Sprite::enableInstancing ( uint32_t maxInstances,
const std::string & instanceVertShaderPath,
const std::string & instanceFragShaderPath )

Enable GPU instancing for this sprite type.

Parameters
maxInstancesMaximum simultaneous instances.
instanceVertShaderPathVertex shader supporting instancing.
instanceFragShaderPathFragment shader.

Definition at line 1167 of file mxvk_sprite.cpp.

1169 {
1170 if (colorAttachmentFormat == VK_FORMAT_UNDEFINED || descriptorSetLayout == VK_NULL_HANDLE) {
1171 throw mxvk::Exception("VKSprite::enableInstancing called before color format/descriptorSetLayout set");
1172 }
1173 ensureInstanceBuffer(maxInstances);
1174 createQuadBuffer();
1175 instanceVertPath = instanceVertShaderPath;
1176 instanceFragPath = instanceFragShaderPath;
1177 createInstancedPipeline(instanceVertShaderPath, instanceFragShaderPath);
1178 instancingEnabled = true;
1179 std::cout << std::format("mxvk: Instancing enabled (max {} instances)\n", maxInstances);
1180 }

◆ enableSpectrumHistoryTexture()

uint32_t mxvk::VK_Sprite::enableSpectrumHistoryTexture ( uint32_t bins,
uint32_t layers )

Allocate a shader-readable FFT spectrum-history array.

Enables extended descriptors and exposes the history as a combined image sampler at set 0, binding 4. The R32_SFLOAT 1-D array is initialized to zero and uses a circular write head.

Parameters
binsNumber of frequency bins in every history layer.
layersRequested number of history layers. The active GPU's maximum image-array-layer limit is applied automatically.
Returns
Number of history layers actually allocated.
Exceptions
mxvk::Exceptionwhen either requested dimension is zero.

Definition at line 546 of file mxvk_sprite.cpp.

546 {
547 if (bins == 0 || layers == 0) {
548 throw mxvk::Exception(
549 "VKSprite::enableSpectrumHistoryTexture requires positive bin and layer counts");
550 }
551 VkPhysicalDeviceProperties properties{};
552 vkGetPhysicalDeviceProperties(physicalDevice, &properties);
553 const uint32_t allocatedLayers =
554 std::min(layers, properties.limits.maxImageArrayLayers);
555 if (allocatedLayers == 0) {
556 throw mxvk::Exception(
557 "VKSprite::enableSpectrumHistoryTexture is unavailable on this device");
558 }
559 if (spectrumHistoryTextureEnabled && spectrumHistoryBins == bins &&
560 spectrumHistoryLayers == allocatedLayers) {
561 return spectrumHistoryLayers;
562 }
563 if (!extendedUBOEnabled) {
565 }
566
567 vkDeviceWaitIdle(device);
568 destroySpectrumHistoryTexture();
569
570 if (allocatedLayers != layers) {
571 std::cerr << "vk: spectrum history clamped to device array-layer limit "
572 << allocatedLayers << " (was " << layers << ")\n";
573 }
574 spectrumHistoryBins = bins;
575 spectrumHistoryLayers = allocatedLayers;
576 spectrumHistoryHead = 0;
577 spectrumHistoryWriteIndex = 0;
578 extendedUBOData.audio_history =
579 glm::vec4(0.0f, static_cast<float>(allocatedLayers),
580 static_cast<float>(bins), 0.0f);
581
582 createImage(bins, 1, VK_FORMAT_R32_SFLOAT, VK_IMAGE_TILING_OPTIMAL,
583 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
584 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spectrumHistoryImage,
585 spectrumHistoryImageMemory, allocatedLayers, VK_IMAGE_TYPE_1D);
586
587 const VkDeviceSize imageSize = static_cast<VkDeviceSize>(bins) *
588 static_cast<VkDeviceSize>(allocatedLayers) *
589 sizeof(float);
590 VkBuffer stagingBuffer = VK_NULL_HANDLE;
591 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
592 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
593 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
594 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
595 stagingBuffer, stagingMemory);
596
597 void *data = nullptr;
598 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
599 memset(data, 0, static_cast<std::size_t>(imageSize));
600 vkUnmapMemory(device, stagingMemory);
601
602 transitionImageLayout(spectrumHistoryImage, VK_IMAGE_LAYOUT_UNDEFINED,
603 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0,
604 allocatedLayers);
605 copyBufferToImage(stagingBuffer, spectrumHistoryImage, bins, 1, 0,
606 allocatedLayers);
607 transitionImageLayout(spectrumHistoryImage,
608 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
609 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 0,
610 allocatedLayers);
611
612 vkDestroyBuffer(device, stagingBuffer, nullptr);
613 vkFreeMemory(device, stagingMemory, nullptr);
614
615 spectrumHistoryImageView =
616 createImageView(spectrumHistoryImage, VK_FORMAT_R32_SFLOAT,
617 VK_IMAGE_VIEW_TYPE_1D_ARRAY, allocatedLayers);
618 spectrumHistoryTextureEnabled = true;
619 recreateExtendedDescriptorLayout();
620 return allocatedLayers;
621 }

◆ enableSpectrumTexture()

void mxvk::VK_Sprite::enableSpectrumTexture ( uint32_t bins)

Allocate a shader-readable 1-D floating-point spectrum texture.

Enables extended descriptors and exposes the texture as a combined image sampler at set 0, binding 3. The texture is initialized to zero.

Parameters
binsNumber of R32_SFLOAT frequency bins.
Exceptions
mxvk::Exceptionwhen bins is zero.

Definition at line 431 of file mxvk_sprite.cpp.

431 {
432 if (bins == 0) {
433 throw mxvk::Exception("VKSprite::enableSpectrumTexture requires a positive bin count");
434 }
435 if (spectrumTextureEnabled && spectrumBins == bins) {
436 return;
437 }
438 if (!extendedUBOEnabled) {
440 }
441
442 vkDeviceWaitIdle(device);
443 destroySpectrumTexture();
444
445 spectrumBins = bins;
446 createImage(bins, 1, VK_FORMAT_R32_SFLOAT, VK_IMAGE_TILING_OPTIMAL,
447 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
448 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, spectrumImage,
449 spectrumImageMemory, 1, VK_IMAGE_TYPE_1D);
450
451 const VkDeviceSize imageSize = static_cast<VkDeviceSize>(bins) * sizeof(float);
452 VkBuffer stagingBuffer = VK_NULL_HANDLE;
453 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
454 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
455 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
456 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
457 stagingBuffer, stagingMemory);
458
459 void *data = nullptr;
460 VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, imageSize, 0, &data));
461 memset(data, 0, static_cast<std::size_t>(imageSize));
462 vkUnmapMemory(device, stagingMemory);
463
464 transitionImageLayout(spectrumImage, VK_IMAGE_LAYOUT_UNDEFINED,
465 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
466 copyBufferToImage(stagingBuffer, spectrumImage, bins, 1);
467 transitionImageLayout(spectrumImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
468 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
469
470 vkDestroyBuffer(device, stagingBuffer, nullptr);
471 vkFreeMemory(device, stagingMemory, nullptr);
472
473 spectrumImageView = createImageView(spectrumImage, VK_FORMAT_R32_SFLOAT,
474 VK_IMAGE_VIEW_TYPE_1D);
475 spectrumTextureEnabled = true;
476 recreateExtendedDescriptorLayout();
477 }

◆ getEffectsEnabled()

bool mxvk::VK_Sprite::getEffectsEnabled ( ) const
inline
Returns
true if shader effects are enabled.

Definition at line 169 of file mxvk_sprite.hpp.

169{ return effectsEnabled; }

◆ getHeight()

int mxvk::VK_Sprite::getHeight ( ) const
inline
Returns
Sprite texture height in pixels.

Definition at line 193 of file mxvk_sprite.hpp.

193{ return spriteHeight; }

◆ getHistoryHead()

uint32_t mxvk::VK_Sprite::getHistoryHead ( ) const
inlinenodiscard
Returns
The logical oldest-layer index for a circular history sampler.

Definition at line 385 of file mxvk_sprite.hpp.

385{ return historyHead; }

◆ getHistoryLayerCount()

uint32_t mxvk::VK_Sprite::getHistoryLayerCount ( ) const
inlinenodiscard
Returns
Number of allocated texture-history layers.

Definition at line 388 of file mxvk_sprite.hpp.

388{ return historyLayers; }

◆ getPipeline()

VkPipeline mxvk::VK_Sprite::getPipeline ( ) const
inline
Returns
The custom VkPipeline handle (may be VK_NULL_HANDLE).

Definition at line 252 of file mxvk_sprite.hpp.

252{ return customPipeline; }

◆ getPipelineLayout()

VkPipelineLayout mxvk::VK_Sprite::getPipelineLayout ( ) const
inline
Returns
The custom pipeline layout handle.

Definition at line 254 of file mxvk_sprite.hpp.

254{ return customPipelineLayout; }

◆ getSpectrumBinCount()

uint32_t mxvk::VK_Sprite::getSpectrumBinCount ( ) const
inlinenodiscard
Returns
Number of allocated spectrum bins.

Definition at line 409 of file mxvk_sprite.hpp.

409{ return spectrumBins; }

◆ getSpectrumHistoryHead()

uint32_t mxvk::VK_Sprite::getSpectrumHistoryHead ( ) const
inlinenodiscard
Returns
Physical array layer containing the newest FFT spectrum.

Definition at line 435 of file mxvk_sprite.hpp.

435{ return spectrumHistoryHead; }

◆ getSpectrumHistoryLayerCount()

uint32_t mxvk::VK_Sprite::getSpectrumHistoryLayerCount ( ) const
inlinenodiscard
Returns
Number of allocated FFT spectrum-history layers.

Definition at line 438 of file mxvk_sprite.hpp.

438{ return spectrumHistoryLayers; }

◆ getWidth()

int mxvk::VK_Sprite::getWidth ( ) const
inline
Returns
Sprite texture width in pixels.

Definition at line 191 of file mxvk_sprite.hpp.

191{ return spriteWidth; }

◆ hasComputePipeline()

bool mxvk::VK_Sprite::hasComputePipeline ( ) const
inlinenodiscard
Returns
Whether this sprite owns an executable compute pipeline.

Definition at line 240 of file mxvk_sprite.hpp.

240 {
241 return computePipeline != VK_NULL_HANDLE;
242 }

◆ hasOwnPipeline()

bool mxvk::VK_Sprite::hasOwnPipeline ( ) const
inline
Returns
true if a custom pipeline has been built.

Definition at line 250 of file mxvk_sprite.hpp.

250{ return customPipeline != VK_NULL_HANDLE; }

◆ isExtendedUBOEnabled()

bool mxvk::VK_Sprite::isExtendedUBOEnabled ( ) const
inline
Returns
true if the extended UBO is active.

Definition at line 280 of file mxvk_sprite.hpp.

280{ return extendedUBOEnabled; }

◆ isInstancingEnabled()

bool mxvk::VK_Sprite::isInstancingEnabled ( ) const
inline
Returns
true if GPU instancing is active.

Definition at line 274 of file mxvk_sprite.hpp.

274{ return instancingEnabled; }

◆ loadSprite() [1/2]

void mxvk::VK_Sprite::loadSprite ( const std::string & pngPath,
const std::string & fragmentShaderPath = "" )

Load sprite texture from a PNG file.

Parameters
pngPathPath to the PNG file.
fragmentShaderPathOptional custom fragment shader (SPIR-V .spv).

Definition at line 1698 of file mxvk_sprite.cpp.

1698 {
1699 SDL_Surface *surface = mxvk::LoadPNG(pngPath.c_str());
1700 if (!surface) {
1701 throw mxvk::Exception("Failed to load sprite image: " + pngPath);
1702 }
1703 loadSprite(surface, fragmentShaderPath);
1704 SDL_DestroySurface(surface);
1705 std::cout << std::format("mxvk: Loaded PNG: {}\n", pngPath);
1706 }
void loadSprite(const std::string &pngPath, const std::string &fragmentShaderPath="")
Load sprite texture from a PNG file.
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
Definition mxvk_png.cpp:103

◆ loadSprite() [2/2]

void mxvk::VK_Sprite::loadSprite ( SDL_Surface * surface,
const std::string & fragmentShaderPath = "" )

Load sprite texture from an SDL_Surface.

Parameters
surfaceSource surface (not consumed).
fragmentShaderPathOptional custom fragment shader.

Definition at line 1708 of file mxvk_sprite.cpp.

1708 {
1709 if (!surface) {
1710 throw mxvk::Exception("VKSprite::loadSprite called with null surface");
1711 }
1712 if (spriteLoaded || spriteImage != VK_NULL_HANDLE || fragmentShaderModule != VK_NULL_HANDLE) {
1713 destroySpriteResources();
1714 }
1715 SDL_Surface *rgbaSurface = convertToRGBA(surface);
1716 if (!rgbaSurface) {
1717 throw mxvk::Exception("Failed to convert sprite surface to RGBA");
1718 }
1719 spriteWidth = rgbaSurface->w;
1720 spriteHeight = rgbaSurface->h;
1721 createSpriteTexture(rgbaSurface);
1722 SDL_DestroySurface(rgbaSurface);
1723 createSampler();
1724 createQuadBuffer();
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 spriteLoaded = true;
1736 std::cout << std::format("mxvk: Loaded surface texture: {}x{}\n", spriteWidth, spriteHeight);
1737 }

◆ operator=() [1/2]

VK_Sprite & mxvk::VK_Sprite::operator= ( const VK_Sprite & )
delete

◆ operator=() [2/2]

VK_Sprite & mxvk::VK_Sprite::operator= ( VK_Sprite && )
delete

◆ prepareForRendering()

void mxvk::VK_Sprite::prepareForRendering ( VkCommandBuffer cmdBuffer)

Record texture barriers that must happen before dynamic rendering begins.

Parameters
cmdBufferCommand buffer currently being recorded outside a rendering instance.

Definition at line 2769 of file mxvk_sprite.cpp.

2769 {
2770#ifdef MXVK_CUDA
2771 recordCudaReadyBarrier(cmdBuffer);
2772#endif
2773 }

◆ rebuildInstancedPipeline()

void mxvk::VK_Sprite::rebuildInstancedPipeline ( )

Destroy and recreate the instanced graphics pipeline.

Definition at line 1659 of file mxvk_sprite.cpp.

1659 {
1660 if (!instancingEnabled || instanceVertPath.empty() || instanceFragPath.empty())
1661 return;
1662 createInstancedPipeline(instanceVertPath, instanceFragPath);
1663 }

◆ rebuildPipeline()

void mxvk::VK_Sprite::rebuildPipeline ( )

Destroy and recreate the custom graphics pipeline.

Definition at line 1504 of file mxvk_sprite.cpp.

1504 {
1505 if (!hasCustomShader || fragmentShaderModule == VK_NULL_HANDLE)
1506 return;
1507 createCustomPipeline();
1508 std::cout << "mxvk: Pipeline rebuilt\n";
1509 }

◆ releaseUploadResources()

void mxvk::VK_Sprite::releaseUploadResources ( )

Release upload/staging resources tied to the current command pool.

Call this before destroying or recreating the command pool.

Definition at line 23 of file mxvk_sprite.cpp.

23 {
24 destroyStagingResources();
25 }

◆ renderSprites()

void mxvk::VK_Sprite::renderSprites ( VkCommandBuffer cmdBuffer,
VkPipelineLayout pipelineLayout,
uint32_t screenWidth,
uint32_t screenHeight )

Record all queued draw commands into the given command buffer.

Parameters
cmdBufferActive command buffer.
pipelineLayoutPipeline layout for push constants/descriptors.
screenWidthCurrent viewport width.
screenHeightCurrent viewport height.

Definition at line 2775 of file mxvk_sprite.cpp.

2776 {
2777 if (drawQueue.empty() || !spriteLoaded || !quadBufferCreated) {
2778 return;
2779 }
2780 if (descriptorSet == VK_NULL_HANDLE) {
2781 descriptorSet = createDescriptorSet(spriteImageView);
2782 if (externalTexture) {
2783 externalDescriptorSets[spriteImageView] = descriptorSet;
2784 }
2785 }
2786
2787 if (instancingEnabled && instancedPipeline != VK_NULL_HANDLE && instanceBuffer != VK_NULL_HANDLE) {
2788 uint32_t instanceCount = static_cast<uint32_t>(drawQueue.size());
2789
2790 if (instanceCount > instanceBufferCapacity) {
2791 ensureInstanceBuffer(instanceCount * 2);
2792 }
2793
2794 SpriteInstanceData *dst = static_cast<SpriteInstanceData *>(instanceBufferMapped);
2795 for (uint32_t i = 0; i < instanceCount; ++i) {
2796 const auto &cmd = drawQueue[i];
2797 dst[i].posX = cmd.x;
2798 dst[i].posY = cmd.y;
2799 dst[i].sizeW = cmd.w;
2800 dst[i].sizeH = cmd.h;
2801 dst[i].params[0] = cmd.params.x;
2802 dst[i].params[1] = cmd.params.y;
2803 dst[i].params[2] = cmd.params.z;
2804 dst[i].params[3] = cmd.params.w;
2805 }
2806
2807 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, instancedPipeline);
2808 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, instancedPipelineLayout,
2809 0, 1, &descriptorSet, 0, nullptr);
2810
2811 VkBuffer buffers[] = {quadVertexBuffer, instanceBuffer};
2812 VkDeviceSize bufOffsets[] = {0, 0};
2813 vkCmdBindVertexBuffers(cmdBuffer, 0, 2, buffers, bufOffsets);
2814 vkCmdBindIndexBuffer(cmdBuffer, quadIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
2815
2816 float screenSize[2] = {static_cast<float>(screenWidth), static_cast<float>(screenHeight)};
2817 vkCmdPushConstants(cmdBuffer, instancedPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT,
2818 0, sizeof(screenSize), screenSize);
2819
2820 vkCmdDrawIndexed(cmdBuffer, 6, instanceCount, 0, 0, 0);
2821 return;
2822 }
2823
2824 VkPipelineLayout layoutToUse = (customPipeline != VK_NULL_HANDLE) ? customPipelineLayout : pipelineLayout;
2825 if (customPipeline != VK_NULL_HANDLE) {
2826 vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, customPipeline);
2827 }
2828
2829 // When extended UBO is enabled, update UBO and bind extended descriptor set
2830 if (extendedUBOEnabled && customPipeline != VK_NULL_HANDLE) {
2831 updateExtendedUBO();
2832 if (extendedDescriptorSet == VK_NULL_HANDLE) {
2833 createExtendedDescriptorSet();
2834 }
2835 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layoutToUse,
2836 0, 1, &extendedDescriptorSet, 0, nullptr);
2837 } else {
2838 vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layoutToUse,
2839 0, 1, &descriptorSet, 0, nullptr);
2840 }
2841
2842 VkBuffer vertexBuffers[] = {quadVertexBuffer};
2843 VkDeviceSize offsets[] = {0};
2844 vkCmdBindVertexBuffers(cmdBuffer, 0, 1, vertexBuffers, offsets);
2845 vkCmdBindIndexBuffer(cmdBuffer, quadIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
2846
2847 for (const auto &cmd : drawQueue) {
2848 struct SpritePushConstants {
2849 float screenWidth;
2850 float screenHeight;
2851 float spritePosX;
2852 float spritePosY;
2853 float spriteSizeW;
2854 float spriteSizeH;
2855 float effectsOn;
2856 float padding2;
2857 float params[4];
2858 } pc{
2859 static_cast<float>(screenWidth), static_cast<float>(screenHeight), cmd.x, cmd.y, cmd.w, cmd.h, effectsEnabled ? 1.0f : 0.0f, cmd.rotation, {cmd.params.x, cmd.params.y, cmd.params.z, cmd.params.w}};
2860
2861 vkCmdPushConstants(cmdBuffer, layoutToUse, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
2862 0, sizeof(SpritePushConstants), &pc);
2863
2864 vkCmdDrawIndexed(cmdBuffer, 6, 1, 0, 0, 0);
2865 }
2866 }

◆ setAudioBands()

void mxvk::VK_Sprite::setAudioBands ( float low,
float mid,
float high,
float reserved = 0.0f )

Upload audio frequency-band energy to the extended UBO.

The values are appended after the custom-uniform array to preserve the existing SpriteExtended prefix and custom-uniform offsets.

Parameters
lowEnergy below 300 Hz.
midEnergy from 300 through 3000 Hz.
highEnergy above 3000 Hz.
reservedReserved channel.

Definition at line 205 of file mxvk_sprite.cpp.

205 {
206 extendedUBOData.audio_bands = glm::vec4(low, mid, high, reserved);
207 }

◆ setColorAttachmentFormat()

void mxvk::VK_Sprite::setColorAttachmentFormat ( VkFormat format)
inline

Assign dynamic-rendering color attachment format used to build pipelines.

Definition at line 206 of file mxvk_sprite.hpp.

206{ colorAttachmentFormat = format; }

◆ setCommandPool()

void mxvk::VK_Sprite::setCommandPool ( VkCommandPool pool)

Rebind the command pool used for upload/staging operations.

Any in-flight upload resources tied to the previous pool are released first.

Definition at line 27 of file mxvk_sprite.cpp.

27 {
28 if (pool == commandPool) {
29 return;
30 }
31
32 // uploadCmdBuffer is allocated from commandPool, so it must be released
33 // before switching to another pool.
34 destroyStagingResources();
35 commandPool = pool;
36 }

◆ setCustomUniforms()

void mxvk::VK_Sprite::setCustomUniforms ( const std::vector< float > & values)

Upload ordered custom float values to the extended UBO.

Custom shaders can append vec4 custom_uniforms[16] after u3 in their binding-1 SpriteExtended block. Value N is available at custom_uniforms[N/4][N%4]. Existing shaders that use only the original SpriteExtended prefix remain compatible.

Parameters
valuesUp to MAX_CUSTOM_UNIFORMS values. Unused slots are zeroed.
Exceptions
mxvk::Exceptionwhen too many values are supplied.

Definition at line 209 of file mxvk_sprite.cpp.

209 {
210 if (values.size() > MAX_CUSTOM_UNIFORMS) {
211 throw mxvk::Exception(std::format(
212 "VKSprite::setCustomUniforms supports at most {} values",
214 }
215
216 extendedUBOData.custom_uniforms.fill(glm::vec4(0.0f));
217 for (std::size_t index = 0; index < values.size(); ++index) {
218 extendedUBOData.custom_uniforms[index / 4][index % 4] = values[index];
219 }
220 }
static constexpr std::size_t MAX_CUSTOM_UNIFORMS

◆ setDepthAttachmentFormat()

void mxvk::VK_Sprite::setDepthAttachmentFormat ( VkFormat format)
inline

Assign dynamic-rendering depth attachment format used to build pipelines.

Definition at line 208 of file mxvk_sprite.hpp.

208{ depthAttachmentFormat = format; }

◆ setDescriptorSetLayout()

void mxvk::VK_Sprite::setDescriptorSetLayout ( VkDescriptorSetLayout layout)
inline

Assign an external descriptor-set layout.

Definition at line 202 of file mxvk_sprite.hpp.

202{ descriptorSetLayout = layout; }

◆ setEffectsEnabled()

void mxvk::VK_Sprite::setEffectsEnabled ( bool enabled)
inline

Enable or disable the custom fragment shader effects.

Parameters
enabledtrue to enable effects.

Definition at line 166 of file mxvk_sprite.hpp.

166{ effectsEnabled = enabled; }

◆ setExternalTexture()

void mxvk::VK_Sprite::setExternalTexture ( VkImageView image_view,
int width,
int height )

Definition at line 2723 of file mxvk_sprite.cpp.

2723 {
2724 if (image_view == VK_NULL_HANDLE || width <= 0 || height <= 0) {
2725 throw mxvk::Exception("VKSprite::setExternalTexture received an invalid image view");
2726 }
2727 if (externalTexture && spriteImageView == image_view) {
2728 spriteWidth = width;
2729 spriteHeight = height;
2730 spriteLoaded = true;
2731 return;
2732 }
2733 auto cached_descriptor = externalDescriptorSets.find(image_view);
2734 descriptorSet = (cached_descriptor != externalDescriptorSets.end()) ? cached_descriptor->second : VK_NULL_HANDLE;
2735 descriptorSetPool = VK_NULL_HANDLE;
2736 if (!externalTexture) {
2737 destroyTextureDescriptorPools();
2738 } else if (extendedDescriptorPool != VK_NULL_HANDLE) {
2739 vkDeviceWaitIdle(device);
2740 vkDestroyDescriptorPool(device, extendedDescriptorPool, nullptr);
2741 extendedDescriptorPool = VK_NULL_HANDLE;
2742 extendedDescriptorSet = VK_NULL_HANDLE;
2743 }
2744 if (!externalTexture && spriteImageView != VK_NULL_HANDLE) {
2745 vkDestroyImageView(device, spriteImageView, nullptr);
2746 }
2747 if (!externalTexture && spriteImage != VK_NULL_HANDLE) {
2748 vkDestroyImage(device, spriteImage, nullptr);
2749 }
2750 if (!externalTexture && spriteImageMemory != VK_NULL_HANDLE) {
2751 vkFreeMemory(device, spriteImageMemory, nullptr);
2752 }
2753 spriteImageView = image_view;
2754 spriteImage = VK_NULL_HANDLE;
2755 spriteImageMemory = VK_NULL_HANDLE;
2756 externalTexture = true;
2757 spriteWidth = width;
2758 spriteHeight = height;
2759 spriteLoaded = true;
2760 }

◆ setFragmentShaderPath()

void mxvk::VK_Sprite::setFragmentShaderPath ( const std::string & path)

Replace the fragment shader path and rebuild the custom pipeline.

Definition at line 1511 of file mxvk_sprite.cpp.

1511 {
1512 if (path == fragmentShaderPath && fragmentShaderModule != VK_NULL_HANDLE) {
1513 return;
1514 }
1515
1516 if (customPipeline != VK_NULL_HANDLE) {
1517 vkDestroyPipeline(device, customPipeline, nullptr);
1518 customPipeline = VK_NULL_HANDLE;
1519 }
1520 if (customPipelineLayout != VK_NULL_HANDLE) {
1521 vkDestroyPipelineLayout(device, customPipelineLayout, nullptr);
1522 customPipelineLayout = VK_NULL_HANDLE;
1523 }
1524 if (fragmentShaderModule != VK_NULL_HANDLE) {
1525 vkDestroyShaderModule(device, fragmentShaderModule, nullptr);
1526 fragmentShaderModule = VK_NULL_HANDLE;
1527 }
1528
1529 fragmentShaderPath = path;
1530 hasCustomShader = false;
1531
1532 if (fragmentShaderPath.empty()) {
1533 return;
1534 }
1535
1536 const auto shaderCode = readShaderFile(fragmentShaderPath);
1537 fragmentShaderModule = mxvk::create_shader_module(device, shaderCode);
1538 hasCustomShader = true;
1539
1540 if (colorAttachmentFormat != VK_FORMAT_UNDEFINED && descriptorSetLayout != VK_NULL_HANDLE) {
1541 createCustomPipeline();
1542 }
1543 }

◆ setMouseState()

void mxvk::VK_Sprite::setMouseState ( float mx,
float my,
float pressed,
float reserved = 0.0f )

Upload mouse state to the extended UBO.

Parameters
mxMouse X (normalised or pixels).
myMouse Y.
pressedMouse button state.
reservedReserved channel.

Definition at line 185 of file mxvk_sprite.cpp.

185 {
186 extendedUBOData.mouse = glm::vec4(mx, my, pressed, reserved);
187 }

◆ setPipelineCache()

void mxvk::VK_Sprite::setPipelineCache ( VkPipelineCache cache)
inline

Use the shared pipeline cache for custom/instanced pipeline creation.

Definition at line 216 of file mxvk_sprite.hpp.

216{ pipelineCache = cache; }

◆ setRenderPass()

void mxvk::VK_Sprite::setRenderPass ( VkRenderPass rp)
inline

Assign the render pass used to build the custom pipeline.

Definition at line 204 of file mxvk_sprite.hpp.

204{ renderPass = rp; }

◆ setShaderParams()

void mxvk::VK_Sprite::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.

Parameters
p1,p2,p3,p4Parameter values packed into a vec4.

Definition at line 2719 of file mxvk_sprite.cpp.

2719 {
2720 shaderParams = glm::vec4(p1, p2, p3, p4);
2721 }

◆ setTextureFilter()

void mxvk::VK_Sprite::setTextureFilter ( VkFilter filter)

Select the hardware filter used when scaling this sprite.

Parameters
filterVK_FILTER_NEAREST for sharp pixels or VK_FILTER_LINEAR for smoothing.

Definition at line 38 of file mxvk_sprite.cpp.

38 {
39 if (filter != VK_FILTER_NEAREST && filter != VK_FILTER_LINEAR) {
40 throw mxvk::Exception("VKSprite::setTextureFilter supports only nearest or linear filtering");
41 }
42 if (textureFilter == filter) {
43 return;
44 }
45
46 textureFilter = filter;
47 if (spriteSampler != VK_NULL_HANDLE) {
48 destroyTextureDescriptorPools();
49 vkDestroySampler(device, spriteSampler, nullptr);
50 spriteSampler = VK_NULL_HANDLE;
51 createSampler();
52 }
53
54 std::cout << "mxvk: Sprite texture filter set to "
55 << (textureFilter == VK_FILTER_NEAREST ? "nearest\n" : "linear\n");
56 }

◆ setUniform0()

void mxvk::VK_Sprite::setUniform0 ( float x,
float y,
float z,
float w )

Upload user uniform 0 to the extended UBO.

Parameters
x,y,z,wComponents.

Definition at line 189 of file mxvk_sprite.cpp.

189 {
190 extendedUBOData.u0 = glm::vec4(x, y, z, w);
191 }

◆ setUniform1()

void mxvk::VK_Sprite::setUniform1 ( float x,
float y,
float z,
float w )

Upload user uniform 1 to the extended UBO.

Parameters
x,y,z,wComponents.

Definition at line 193 of file mxvk_sprite.cpp.

193 {
194 extendedUBOData.u1 = glm::vec4(x, y, z, w);
195 }

◆ setUniform2()

void mxvk::VK_Sprite::setUniform2 ( float x,
float y,
float z,
float w )

Upload user uniform 2 to the extended UBO.

Parameters
x,y,z,wComponents.

Definition at line 197 of file mxvk_sprite.cpp.

197 {
198 extendedUBOData.u2 = glm::vec4(x, y, z, w);
199 }

◆ setUniform3()

void mxvk::VK_Sprite::setUniform3 ( float x,
float y,
float z,
float w )

Upload user uniform 3 to the extended UBO.

Parameters
x,y,z,wComponents.

Definition at line 201 of file mxvk_sprite.cpp.

201 {
202 extendedUBOData.u3 = glm::vec4(x, y, z, w);
203 }

◆ setVertexShaderPath()

void mxvk::VK_Sprite::setVertexShaderPath ( const std::string & path)
inline

Override the vertex shader path (used when rebuilding the pipeline).

Definition at line 224 of file mxvk_sprite.hpp.

224{ vertexShaderPath = path; }

◆ shareHistoryTexture()

void mxvk::VK_Sprite::shareHistoryTexture ( const VK_Sprite & source)

Bind another sprite's history array without taking ownership.

The source sprite must outlive this sprite. This is intended for post-processing passes which read one shared input history ring. No image, memory, or image view is allocated or freed by this sprite.

Parameters
sourceSprite which owns an enabled history texture.
Exceptions
mxvk::Exceptionwhen the source has no history texture or belongs to a different Vulkan device.

Definition at line 313 of file mxvk_sprite.cpp.

313 {
314 if (source.device != device) {
315 throw mxvk::Exception(
316 "VKSprite::shareHistoryTexture requires sprites on the same Vulkan device");
317 }
318 if (!source.historyTextureEnabled ||
319 source.historyImageView == VK_NULL_HANDLE ||
320 source.historyLayers == 0) {
321 throw mxvk::Exception(
322 "VKSprite::shareHistoryTexture source has no enabled history texture");
323 }
324 if (&source == this) {
325 throw mxvk::Exception(
326 "VKSprite::shareHistoryTexture cannot share a sprite with itself");
327 }
328
329 if (!extendedUBOEnabled) {
331 }
332
333 vkDeviceWaitIdle(device);
334 destroyHistoryTexture();
335 historyImageView = source.historyImageView;
336 historyWidth = source.historyWidth;
337 historyHeight = source.historyHeight;
338 historyLayers = source.historyLayers;
339 historyHead = source.historyHead;
340 historyTextureEnabled = true;
341 historyTextureShared = true;
342 recreateExtendedDescriptorLayout();
343 }

◆ updateHistoryTexture()

void mxvk::VK_Sprite::updateHistoryTexture ( const void * pixels,
int width,
int height,
int pitch = 0 )

Upload one RGBA frame into the next history layer.

The write head advances after a successful upload. Input dimensions must match those passed to enableHistoryTexture().

Parameters
pixelsRGBA8 source pixels.
widthSource width in pixels.
heightSource height in pixels.
pitchSource row stride in bytes, or zero for tightly packed data.
Exceptions
mxvk::Exceptionfor null data, invalid dimensions, or an inactive cache.

Definition at line 345 of file mxvk_sprite.cpp.

345 {
346 if (!historyTextureEnabled || historyImage == VK_NULL_HANDLE) {
347 throw mxvk::Exception("VKSprite::updateHistoryTexture called before enableHistoryTexture");
348 }
349 if (pixels == nullptr) {
350 throw mxvk::Exception("VKSprite::updateHistoryTexture called with null pixel data");
351 }
352 if (width <= 0 || height <= 0 || static_cast<uint32_t>(width) != historyWidth ||
353 static_cast<uint32_t>(height) != historyHeight) {
354 throw mxvk::Exception("VKSprite::updateHistoryTexture dimensions do not match the history texture");
355 }
356
357 const int sourcePitch = pitch > 0 ? pitch : width * 4;
358 if (sourcePitch < width * 4) {
359 throw mxvk::Exception("VKSprite::updateHistoryTexture pitch is smaller than one RGBA row");
360 }
361
362 const VkDeviceSize imageSize = static_cast<VkDeviceSize>(width) * height * 4;
363 createStagingResources(imageSize);
364 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
365 VK_CHECK_RESULT(vkResetFences(device, 1, &uploadFence));
366
367 if (sourcePitch == width * 4) {
368 memcpy(persistentStagingMapped, pixels, static_cast<std::size_t>(imageSize));
369 } else {
370 const auto *source = static_cast<const uint8_t *>(pixels);
371 auto *destination = static_cast<uint8_t *>(persistentStagingMapped);
372 for (int row = 0; row < height; ++row) {
373 memcpy(destination + static_cast<std::size_t>(row * width * 4),
374 source + static_cast<std::size_t>(row * sourcePitch),
375 static_cast<std::size_t>(width * 4));
376 }
377 }
378
379 VK_CHECK_RESULT(vkResetCommandBuffer(uploadCmdBuffer, 0));
380 VkCommandBufferBeginInfo beginInfo{};
381 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
382 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
383 VK_CHECK_RESULT(vkBeginCommandBuffer(uploadCmdBuffer, &beginInfo));
384
385 VkImageMemoryBarrier barrier{};
386 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
387 barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
388 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
389 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
390 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
391 barrier.image = historyImage;
392 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
393 barrier.subresourceRange.baseMipLevel = 0;
394 barrier.subresourceRange.levelCount = 1;
395 barrier.subresourceRange.baseArrayLayer = historyHead;
396 barrier.subresourceRange.layerCount = 1;
397 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
398 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
399 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
400 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1,
401 &barrier);
402
403 VkBufferImageCopy region{};
404 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
405 region.imageSubresource.mipLevel = 0;
406 region.imageSubresource.baseArrayLayer = historyHead;
407 region.imageSubresource.layerCount = 1;
408 region.imageExtent = {historyWidth, historyHeight, 1};
409 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer, historyImage,
410 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
411
412 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
413 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
414 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
415 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
416 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
417 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr,
418 1, &barrier);
419
420 VK_CHECK_RESULT(vkEndCommandBuffer(uploadCmdBuffer));
421 VkSubmitInfo submitInfo{};
422 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
423 submitInfo.commandBufferCount = 1;
424 submitInfo.pCommandBuffers = &uploadCmdBuffer;
425 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
426 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
427
428 historyHead = (historyHead + 1) % historyLayers;
429 }

◆ updateSpectrumHistoryTexture()

void mxvk::VK_Sprite::updateSpectrumHistoryTexture ( const float * magnitudes,
uint32_t bins )

Upload one FFT spectrum into the next history layer.

Parameters
magnitudesPointer to bins frequency magnitudes.
binsNumber of values; must match the configured history.

Definition at line 623 of file mxvk_sprite.cpp.

623 {
624 if (!spectrumHistoryTextureEnabled || spectrumHistoryImage == VK_NULL_HANDLE) {
625 throw mxvk::Exception(
626 "VKSprite::updateSpectrumHistoryTexture called before enableSpectrumHistoryTexture");
627 }
628 if (magnitudes == nullptr) {
629 throw mxvk::Exception(
630 "VKSprite::updateSpectrumHistoryTexture called with null data");
631 }
632 if (bins != spectrumHistoryBins) {
633 throw mxvk::Exception(
634 "VKSprite::updateSpectrumHistoryTexture bin count does not match the history texture");
635 }
636
637 const VkDeviceSize imageSize = static_cast<VkDeviceSize>(bins) * sizeof(float);
638 createStagingResources(imageSize);
639 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
640 VK_CHECK_RESULT(vkResetFences(device, 1, &uploadFence));
641 memcpy(persistentStagingMapped, magnitudes, static_cast<std::size_t>(imageSize));
642
643 VK_CHECK_RESULT(vkResetCommandBuffer(uploadCmdBuffer, 0));
644 VkCommandBufferBeginInfo beginInfo{};
645 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
646 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
647 VK_CHECK_RESULT(vkBeginCommandBuffer(uploadCmdBuffer, &beginInfo));
648
649 VkImageMemoryBarrier barrier{};
650 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
651 barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
652 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
653 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
654 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
655 barrier.image = spectrumHistoryImage;
656 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
657 barrier.subresourceRange.baseMipLevel = 0;
658 barrier.subresourceRange.levelCount = 1;
659 barrier.subresourceRange.baseArrayLayer = spectrumHistoryWriteIndex;
660 barrier.subresourceRange.layerCount = 1;
661 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
662 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
663 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
664 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,
665 nullptr, 1, &barrier);
666
667 VkBufferImageCopy region{};
668 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
669 region.imageSubresource.mipLevel = 0;
670 region.imageSubresource.baseArrayLayer = spectrumHistoryWriteIndex;
671 region.imageSubresource.layerCount = 1;
672 region.imageExtent = {bins, 1, 1};
673 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer,
674 spectrumHistoryImage,
675 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
676
677 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
678 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
679 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
680 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
681 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
682 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr,
683 0, nullptr, 1, &barrier);
684
685 VK_CHECK_RESULT(vkEndCommandBuffer(uploadCmdBuffer));
686 VkSubmitInfo submitInfo{};
687 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
688 submitInfo.commandBufferCount = 1;
689 submitInfo.pCommandBuffers = &uploadCmdBuffer;
690 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
691 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
692
693 spectrumHistoryHead = spectrumHistoryWriteIndex;
694 spectrumHistoryWriteIndex =
695 (spectrumHistoryWriteIndex + 1) % spectrumHistoryLayers;
696 extendedUBOData.audio_history =
697 glm::vec4(static_cast<float>(spectrumHistoryHead),
698 static_cast<float>(spectrumHistoryLayers),
699 static_cast<float>(spectrumHistoryBins), 0.0f);
700 }

◆ updateSpectrumTexture()

void mxvk::VK_Sprite::updateSpectrumTexture ( const float * magnitudes,
uint32_t bins )

Replace the current floating-point spectrum data.

Parameters
magnitudesPointer to bins frequency magnitudes.
binsNumber of values; must match enableSpectrumTexture().

Definition at line 479 of file mxvk_sprite.cpp.

479 {
480 if (!spectrumTextureEnabled || spectrumImage == VK_NULL_HANDLE) {
481 throw mxvk::Exception("VKSprite::updateSpectrumTexture called before enableSpectrumTexture");
482 }
483 if (magnitudes == nullptr) {
484 throw mxvk::Exception("VKSprite::updateSpectrumTexture called with null data");
485 }
486 if (bins != spectrumBins) {
487 throw mxvk::Exception("VKSprite::updateSpectrumTexture bin count does not match the spectrum texture");
488 }
489
490 const VkDeviceSize imageSize = static_cast<VkDeviceSize>(bins) * sizeof(float);
491 createStagingResources(imageSize);
492 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
493 VK_CHECK_RESULT(vkResetFences(device, 1, &uploadFence));
494 memcpy(persistentStagingMapped, magnitudes, static_cast<std::size_t>(imageSize));
495
496 VK_CHECK_RESULT(vkResetCommandBuffer(uploadCmdBuffer, 0));
497 VkCommandBufferBeginInfo beginInfo{};
498 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
499 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
500 VK_CHECK_RESULT(vkBeginCommandBuffer(uploadCmdBuffer, &beginInfo));
501
502 VkImageMemoryBarrier barrier{};
503 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
504 barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
505 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
506 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
507 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
508 barrier.image = spectrumImage;
509 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
510 barrier.subresourceRange.baseMipLevel = 0;
511 barrier.subresourceRange.levelCount = 1;
512 barrier.subresourceRange.baseArrayLayer = 0;
513 barrier.subresourceRange.layerCount = 1;
514 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
515 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
516 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
517 VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1,
518 &barrier);
519
520 VkBufferImageCopy region{};
521 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
522 region.imageSubresource.mipLevel = 0;
523 region.imageSubresource.baseArrayLayer = 0;
524 region.imageSubresource.layerCount = 1;
525 region.imageExtent = {bins, 1, 1};
526 vkCmdCopyBufferToImage(uploadCmdBuffer, persistentStagingBuffer, spectrumImage,
527 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
528
529 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
530 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
531 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
532 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
533 vkCmdPipelineBarrier(uploadCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
534 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr,
535 1, &barrier);
536
537 VK_CHECK_RESULT(vkEndCommandBuffer(uploadCmdBuffer));
538 VkSubmitInfo submitInfo{};
539 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
540 submitInfo.commandBufferCount = 1;
541 submitInfo.pCommandBuffers = &uploadCmdBuffer;
542 VK_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, uploadFence));
543 VK_CHECK_RESULT(vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX));
544 }

◆ updateTexture() [1/2]

void mxvk::VK_Sprite::updateTexture ( const void * pixels,
int width,
int height,
int pitch = 0 )

Replace the sprite texture from a raw pixel buffer.

Parameters
pixelsPointer to RGBA data.
widthBuffer width.
heightBuffer height.
pitchRow stride in bytes (0 = auto).

Definition at line 1864 of file mxvk_sprite.cpp.

1864 {
1865 if (!pixels) {
1866 throw mxvk::Exception("VKSprite::updateTexture called with null pixel data");
1867 }
1868 if (!spriteLoaded) {
1869 throw mxvk::Exception("VKSprite::updateTexture called before sprite was loaded");
1870 }
1871 if (width <= 0 || height <= 0) {
1872 throw mxvk::Exception("VKSprite::updateTexture invalid dimensions");
1873 }
1874 int srcPitch = (pitch > 0) ? pitch : width * 4;
1875 if (width == spriteWidth && height == spriteHeight && srcPitch == width * 4) {
1876#ifdef MXVK_CUDA
1877 if (updateTextureCudaHost(pixels, static_cast<uint32_t>(width), static_cast<uint32_t>(height), static_cast<uint32_t>(srcPitch))) {
1878 return;
1879 }
1880#endif
1881 updateSpriteTexture(pixels, width, height);
1882 } else if (width == spriteWidth && height == spriteHeight) {
1883#ifdef MXVK_CUDA
1884 if (updateTextureCudaHost(pixels, static_cast<uint32_t>(width), static_cast<uint32_t>(height), static_cast<uint32_t>(srcPitch))) {
1885 return;
1886 }
1887#endif
1888 std::vector<uint8_t> packed(width * height * 4);
1889 const uint8_t *src = static_cast<const uint8_t *>(pixels);
1890 for (int row = 0; row < height; ++row) {
1891 memcpy(packed.data() + row * width * 4, src + row * srcPitch, width * 4);
1892 }
1893 updateSpriteTexture(packed.data(), width, height);
1894 } else {
1895 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
1896 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1897 }
1898#ifdef MXVK_CUDA
1899 destroyCudaInterop();
1900#endif
1901 destroyTextureDescriptorPools();
1902 if (spriteImageView != VK_NULL_HANDLE) {
1903 vkDestroyImageView(device, spriteImageView, nullptr);
1904 spriteImageView = VK_NULL_HANDLE;
1905 }
1906 if (spriteImage != VK_NULL_HANDLE) {
1907 vkDestroyImage(device, spriteImage, nullptr);
1908 spriteImage = VK_NULL_HANDLE;
1909 }
1910 if (spriteImageMemory != VK_NULL_HANDLE) {
1911 vkFreeMemory(device, spriteImageMemory, nullptr);
1912 spriteImageMemory = VK_NULL_HANDLE;
1913 }
1914 spriteWidth = width;
1915 spriteHeight = height;
1916 std::vector<uint8_t> packed;
1917 const void *texData = pixels;
1918 if (srcPitch != width * 4) {
1919 packed.resize(width * height * 4);
1920 const uint8_t *src = static_cast<const uint8_t *>(pixels);
1921 for (int row = 0; row < height; ++row) {
1922 memcpy(packed.data() + row * width * 4, src + row * srcPitch, width * 4);
1923 }
1924 texData = packed.data();
1925 }
1926 // Resize path: wrap raw pixels in a temporary SDL3 surface (no copy)
1927 SDL_Surface *tmpSurface = SDL_CreateSurfaceFrom(
1928 width, height, SDL_PIXELFORMAT_RGBA32,
1929 const_cast<void *>(texData), width * 4);
1930 if (!tmpSurface) {
1931 throw mxvk::Exception("VKSprite::updateTexture failed to create temp surface");
1932 }
1933 createSpriteTexture(tmpSurface);
1934 SDL_DestroySurface(tmpSurface);
1935 createDescriptorPool();
1936 }
1937 }

◆ updateTexture() [2/2]

void mxvk::VK_Sprite::updateTexture ( SDL_Surface * surface)

Replace the sprite texture from an SDL_Surface.

Parameters
surfaceNew surface (not consumed).

Definition at line 1816 of file mxvk_sprite.cpp.

1816 {
1817 if (!surface) {
1818 throw mxvk::Exception("VKSprite::updateTexture called with null surface");
1819 }
1820 if (!spriteLoaded) {
1821 throw mxvk::Exception("VKSprite::updateTexture called before sprite was loaded");
1822 }
1823 SDL_Surface *rgbaSurface = convertToRGBA(surface);
1824 if (!rgbaSurface) {
1825 throw mxvk::Exception("Failed to convert surface to RGBA in updateTexture");
1826 }
1827 if (rgbaSurface->w == spriteWidth && rgbaSurface->h == spriteHeight) {
1828#ifdef MXVK_CUDA
1829 if (updateTextureCudaHost(rgbaSurface->pixels, static_cast<uint32_t>(rgbaSurface->w), static_cast<uint32_t>(rgbaSurface->h),
1830 static_cast<uint32_t>(rgbaSurface->pitch))) {
1831 SDL_DestroySurface(rgbaSurface);
1832 return;
1833 }
1834#endif
1835 updateSpriteTexture(rgbaSurface->pixels, rgbaSurface->w, rgbaSurface->h);
1836 } else {
1837 if (stagingResourcesCreated && uploadFence != VK_NULL_HANDLE) {
1838 vkWaitForFences(device, 1, &uploadFence, VK_TRUE, UINT64_MAX);
1839 }
1840#ifdef MXVK_CUDA
1841 destroyCudaInterop();
1842#endif
1843 destroyTextureDescriptorPools();
1844 if (spriteImageView != VK_NULL_HANDLE) {
1845 vkDestroyImageView(device, spriteImageView, nullptr);
1846 spriteImageView = VK_NULL_HANDLE;
1847 }
1848 if (spriteImage != VK_NULL_HANDLE) {
1849 vkDestroyImage(device, spriteImage, nullptr);
1850 spriteImage = VK_NULL_HANDLE;
1851 }
1852 if (spriteImageMemory != VK_NULL_HANDLE) {
1853 vkFreeMemory(device, spriteImageMemory, nullptr);
1854 spriteImageMemory = VK_NULL_HANDLE;
1855 }
1856 spriteWidth = rgbaSurface->w;
1857 spriteHeight = rgbaSurface->h;
1858 createSpriteTexture(rgbaSurface);
1859 createDescriptorPool();
1860 }
1861 SDL_DestroySurface(rgbaSurface);
1862 }

Member Data Documentation

◆ MAX_CUSTOM_UNIFORMS

std::size_t mxvk::VK_Sprite::MAX_CUSTOM_UNIFORMS = 64
staticconstexpr

Definition at line 58 of file mxvk_sprite.hpp.

◆ spriteSampler

VkSampler mxvk::VK_Sprite::spriteSampler = VK_NULL_HANDLE

Texture sampler.

Definition at line 261 of file mxvk_sprite.hpp.


The documentation for this class was generated from the following files: