17 throw mxvk::Exception(
"mxvk resource helper requires a valid Vulkan device");
24 throw mxvk::Exception(
"mxvk upload helper requires a valid graphics queue and command pool");
29 VkSamplerCreateInfo sampler_info{};
30 sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
31 sampler_info.magFilter = VK_FILTER_LINEAR;
32 sampler_info.minFilter = VK_FILTER_LINEAR;
33 sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
34 sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
35 sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
36 sampler_info.anisotropyEnable = VK_FALSE;
37 sampler_info.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
38 sampler_info.unnormalizedCoordinates = VK_FALSE;
39 sampler_info.compareEnable = VK_FALSE;
40 sampler_info.compareOp = VK_COMPARE_OP_ALWAYS;
41 sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
42 if (vkCreateSampler(device, &sampler_info,
nullptr, &texture.
sampler) != VK_SUCCESS) {
43 throw mxvk::Exception(
"mxvk resource helper failed to create texture sampler");
50 VkMemoryPropertyFlags properties) {
51 if (physical_device == VK_NULL_HANDLE) {
52 throw mxvk::Exception(
"find_memory_type requires a valid physical device");
55 VkPhysicalDeviceMemoryProperties memory_properties{};
56 vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties);
57 for (uint32_t i = 0; i < memory_properties.memoryTypeCount; ++i) {
58 if ((type_filter & (1U << i)) &&
59 (memory_properties.memoryTypes[i].propertyFlags & properties) == properties) {
69 VkBufferUsageFlags usage,
70 VkMemoryPropertyFlags properties,
72 validate_context(context);
78 VkBufferCreateInfo buffer_info{};
79 buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
80 buffer_info.size = size;
81 buffer_info.usage = usage;
82 buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
83 if (vkCreateBuffer(context.
device, &buffer_info,
nullptr, &buffer.
buffer) != VK_SUCCESS) {
87 VkMemoryRequirements requirements{};
88 vkGetBufferMemoryRequirements(context.
device, buffer.
buffer, &requirements);
90 VkMemoryAllocateInfo allocation{};
91 allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
92 allocation.allocationSize = requirements.size;
95 if (vkAllocateMemory(context.
device, &allocation,
nullptr, &buffer.
memory) != VK_SUCCESS) {
98 if (vkBindBufferMemory(context.
device, buffer.
buffer, buffer.
memory, 0) != VK_SUCCESS) {
109 if (device == VK_NULL_HANDLE) {
114 if (buffer.
buffer != VK_NULL_HANDLE) {
115 vkDestroyBuffer(device, buffer.
buffer,
nullptr);
117 if (buffer.
memory != VK_NULL_HANDLE) {
118 vkFreeMemory(device, buffer.
memory,
nullptr);
124 if (buffer.
mapped !=
nullptr) {
127 if (device == VK_NULL_HANDLE || buffer.
memory == VK_NULL_HANDLE || buffer.
size == 0) {
128 throw mxvk::Exception(
"map_buffer requires a valid buffer allocation");
130 if (vkMapMemory(device, buffer.
memory, 0, buffer.
size, 0, &buffer.
mapped) != VK_SUCCESS || buffer.
mapped ==
nullptr) {
136 if (device != VK_NULL_HANDLE && buffer.
memory != VK_NULL_HANDLE && buffer.
mapped !=
nullptr) {
137 vkUnmapMemory(device, buffer.
memory);
146 VkImageTiling tiling,
147 VkImageUsageFlags usage,
148 VkMemoryPropertyFlags properties,
150 VkDeviceMemory &memory) {
151 validate_context(context);
152 if (width == 0 || height == 0) {
155 if (image != VK_NULL_HANDLE) {
156 vkDestroyImage(context.
device, image,
nullptr);
157 image = VK_NULL_HANDLE;
159 if (memory != VK_NULL_HANDLE) {
160 vkFreeMemory(context.
device, memory,
nullptr);
161 memory = VK_NULL_HANDLE;
164 VkImageCreateInfo image_info{};
165 image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
166 image_info.imageType = VK_IMAGE_TYPE_2D;
167 image_info.extent = {width, height, 1};
168 image_info.mipLevels = 1;
169 image_info.arrayLayers = 1;
170 image_info.format = format;
171 image_info.tiling = tiling;
172 image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
173 image_info.usage = usage;
174 image_info.samples = VK_SAMPLE_COUNT_1_BIT;
175 image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
176 if (vkCreateImage(context.
device, &image_info,
nullptr, &image) != VK_SUCCESS) {
180 VkMemoryRequirements requirements{};
181 vkGetImageMemoryRequirements(context.
device, image, &requirements);
183 VkMemoryAllocateInfo allocation{};
184 allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
185 allocation.allocationSize = requirements.size;
188 if (vkAllocateMemory(context.
device, &allocation,
nullptr, &memory) != VK_SUCCESS) {
191 if (vkBindImageMemory(context.
device, image, memory, 0) != VK_SUCCESS) {
195 if (memory != VK_NULL_HANDLE) {
196 vkFreeMemory(context.
device, memory,
nullptr);
197 memory = VK_NULL_HANDLE;
199 if (image != VK_NULL_HANDLE) {
200 vkDestroyImage(context.
device, image,
nullptr);
201 image = VK_NULL_HANDLE;
210 VkImageAspectFlags aspect) {
211 VkImageViewCreateInfo view_info{};
212 view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
213 view_info.image = image;
214 view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
215 view_info.format = format;
216 view_info.subresourceRange.aspectMask = aspect;
217 view_info.subresourceRange.baseMipLevel = 0;
218 view_info.subresourceRange.levelCount = 1;
219 view_info.subresourceRange.baseArrayLayer = 0;
220 view_info.subresourceRange.layerCount = 1;
222 VkImageView image_view = VK_NULL_HANDLE;
223 if (vkCreateImageView(device, &view_info,
nullptr, &image_view) != VK_SUCCESS) {
230 validate_upload_context(context);
231 VkCommandBufferAllocateInfo alloc_info{};
232 alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
233 alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
235 alloc_info.commandBufferCount = 1;
237 VkCommandBuffer command_buffer = VK_NULL_HANDLE;
238 if (vkAllocateCommandBuffers(context.
device, &alloc_info, &command_buffer) != VK_SUCCESS) {
242 VkCommandBufferBeginInfo begin_info{};
243 begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
244 begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
245 if (vkBeginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS) {
250 return command_buffer;
254 validate_upload_context(context);
255 if (vkEndCommandBuffer(command_buffer) != VK_SUCCESS) {
260 VkCommandBufferSubmitInfo command_buffer_info{};
261 command_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
262 command_buffer_info.commandBuffer = command_buffer;
264 VkSubmitInfo2 submit_info{};
265 submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
266 submit_info.commandBufferInfoCount = 1;
267 submit_info.pCommandBufferInfos = &command_buffer_info;
269 if (vkQueueSubmit2(context.
graphics_queue, 1, &submit_info, VK_NULL_HANDLE) != VK_SUCCESS) {
275 throw mxvk::Exception(
"failed to wait for one-time command completion");
284 VkBuffer destination,
292 VkBufferCopy2 copy_region{};
293 copy_region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
294 copy_region.size = size;
296 VkCopyBufferInfo2 copy_info{};
297 copy_info.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2;
298 copy_info.srcBuffer = source;
299 copy_info.dstBuffer = destination;
300 copy_info.regionCount = 1;
301 copy_info.pRegions = ©_region;
302 vkCmdCopyBuffer2(command_buffer, ©_info);
309 VkImageLayout old_layout,
310 VkImageLayout new_layout,
311 VkImageAspectFlags aspect) {
312 VkImageMemoryBarrier2 barrier{};
313 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
314 barrier.oldLayout = old_layout;
315 barrier.newLayout = new_layout;
316 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
317 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
318 barrier.image = image;
319 barrier.subresourceRange.aspectMask = aspect;
320 barrier.subresourceRange.baseMipLevel = 0;
321 barrier.subresourceRange.levelCount = 1;
322 barrier.subresourceRange.baseArrayLayer = 0;
323 barrier.subresourceRange.layerCount = 1;
326 if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED && new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
327 barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE;
328 barrier.srcAccessMask = VK_ACCESS_2_NONE;
329 barrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
330 barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
331 }
else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
332 barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
333 barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
334 barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
335 barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
340 VkDependencyInfo dependency{};
341 dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
342 dependency.imageMemoryBarrierCount = 1;
343 dependency.pImageMemoryBarriers = &barrier;
344 vkCmdPipelineBarrier2(command_buffer, &dependency);
352 VkBufferImageCopy2 region{};
353 region.sType = VK_STRUCTURE_TYPE_BUFFER_IMAGE_COPY_2;
354 region.bufferOffset = 0;
355 region.bufferRowLength = 0;
356 region.bufferImageHeight = 0;
357 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
358 region.imageSubresource.mipLevel = 0;
359 region.imageSubresource.baseArrayLayer = 0;
360 region.imageSubresource.layerCount = 1;
361 region.imageOffset = {0, 0, 0};
362 region.imageExtent = {width, height, 1};
364 VkCopyBufferToImageInfo2 copy_info{};
365 copy_info.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_TO_IMAGE_INFO_2;
366 copy_info.srcBuffer = buffer;
367 copy_info.dstImage = image;
368 copy_info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
369 copy_info.regionCount = 1;
370 copy_info.pRegions = ®ion;
371 vkCmdCopyBufferToImage2(command_buffer, ©_info);
378 validate_upload_context(context);
380 throw mxvk::Exception(
"create_texture_from_surface requires a valid SDL surface");
384 const uint32_t width =
static_cast<uint32_t
>(
surface->w);
385 const uint32_t height =
static_cast<uint32_t
>(
surface->h);
386 const VkDeviceSize image_size =
static_cast<VkDeviceSize
>(width) *
static_cast<VkDeviceSize
>(height) * 4U;
387 std::vector<std::byte> tight_pixels(
static_cast<size_t>(image_size));
390 const auto *src =
static_cast<const std::byte *
>(
surface->pixels);
391 auto *dst = tight_pixels.data();
392 const size_t tight_row_bytes =
static_cast<size_t>(width) * 4U;
393 for (uint32_t y = 0; y < height; ++y) {
394 std::memcpy(dst + y * tight_row_bytes, src +
static_cast<size_t>(y) *
static_cast<size_t>(
surface->pitch), tight_row_bytes);
401 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
402 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
405 std::memcpy(staging.
mapped, tight_pixels.data(), tight_pixels.size());
412 VK_IMAGE_TILING_OPTIMAL,
413 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
414 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
427 create_sampler(context.
device, texture);
428 texture.
width = width;
438 const std::string &path,
441 std::unique_ptr<SDL_Surface,
decltype(&SDL_DestroySurface)>
surface(
mxvk::LoadPNG(path.c_str()), SDL_DestroySurface);
443 throw mxvk::Exception(std::format(
"failed to load texture PNG: {}", path));
449 if (device == VK_NULL_HANDLE) {
453 if (texture.
sampler != VK_NULL_HANDLE) {
454 vkDestroySampler(device, texture.
sampler,
nullptr);
456 if (texture.
view != VK_NULL_HANDLE) {
457 vkDestroyImageView(device, texture.
view,
nullptr);
459 if (texture.
image != VK_NULL_HANDLE) {
460 vkDestroyImage(device, texture.
image,
nullptr);
462 if (texture.
memory != VK_NULL_HANDLE) {
463 vkFreeMemory(device, texture.
memory,
nullptr);
PNG image loading and saving utilities via SDL3.
Reusable Vulkan buffer, image, upload, and one-shot command helpers.
void validate_upload_context(const VulkanContext &context)
void create_sampler(VkDevice device, TextureResource &texture)
void validate_context(const VulkanContext &context)
Utilities for loading and saving PNG images.
void transition_image_layout(VkCommandBuffer command_buffer, VkImage image, VkImageLayout old_layout, VkImageLayout new_layout, VkImageAspectFlags aspect=VK_IMAGE_ASPECT_COLOR_BIT)
Record a Vulkan 1.3 synchronization2 image layout transition.
void create_texture_from_png(const VulkanContext &context, const std::string &path, TextureResource &texture, VkFormat format=VK_FORMAT_R8G8B8A8_UNORM)
Load a PNG and upload it into a sampled 2D texture.
void create_texture_from_surface(const VulkanContext &context, SDL_Surface *surface, TextureResource &texture, VkFormat format=VK_FORMAT_R8G8B8A8_UNORM)
Upload an SDL surface into a sampled 2D texture.
void copy_buffer(const VulkanContext &context, VkBuffer source, VkBuffer destination, VkDeviceSize size)
Copy one buffer into another with a one-shot Vulkan 1.3 copy command.
void create_image(const VulkanContext &context, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &memory)
Create a 2D VkImage and allocate/bind its memory.
VkImageView create_image_view(VkDevice device, VkImage image, VkFormat format, VkImageAspectFlags aspect)
Create a 2D image view for an image.
uint32_t find_memory_type(VkPhysicalDevice physical_device, uint32_t type_filter, VkMemoryPropertyFlags properties)
Find a memory type satisfying a Vulkan memory type mask and property set.
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
void unmap_buffer(VkDevice device, BufferResource &buffer)
Unmap a BufferResource if it is currently mapped.
void map_buffer(VkDevice device, BufferResource &buffer)
Persistently map a host-visible buffer allocation.
void create_buffer(const VulkanContext &context, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, BufferResource &buffer)
Create and bind a Vulkan buffer allocation.
VkCommandBuffer begin_one_time_commands(const VulkanContext &context)
Allocate and begin a primary one-time command buffer.
void end_one_time_commands(const VulkanContext &context, VkCommandBuffer command_buffer)
End, submit with vkQueueSubmit2, wait idle, and free a one-time command buffer.
void destroy_buffer(VkDevice device, BufferResource &buffer)
Unmap and destroy a BufferResource.
void copy_buffer_to_image(VkCommandBuffer command_buffer, VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
Record a Vulkan 1.3 vkCmdCopyBufferToImage2 copy for a full 2D image.
void destroy_texture(VkDevice device, TextureResource &texture)
Destroy every Vulkan handle owned by a TextureResource.
Owned Vulkan buffer allocation with optional persistent host mapping.
VkDeviceMemory memory
Device memory bound to buffer.
VkBuffer buffer
Vulkan buffer handle.
VkDeviceSize size
Requested buffer size in bytes.
void * mapped
Host pointer returned by vkMapMemory, or nullptr when unmapped.
Owned sampled 2D texture resources.
uint32_t width
Texture width in pixels.
VkImage image
Optimal-tiled image containing the texture pixels.
VkImageView view
2D color image view used by descriptors.
uint32_t height
Texture height in pixels.
VkSampler sampler
Sampler configured for linear filtering and clamp-to-edge addressing.
VkDeviceMemory memory
Device-local memory bound to image.
Minimal Vulkan handles required by MXVK resource helpers.
VkDevice device
Logical device used to create and destroy resources.
VkCommandPool command_pool
Command pool used for transient upload command buffers.
VkPhysicalDevice physical_device
Physical device used for memory type queries.
VkQueue graphics_queue
Queue used for one-shot upload command submission.