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");
48 uint32_t
find_memory_type(VkPhysicalDevice physical_device, uint32_t type_filter, VkMemoryPropertyFlags properties) {
49 if (physical_device == VK_NULL_HANDLE) {
50 throw mxvk::Exception(
"find_memory_type requires a valid physical device");
53 VkPhysicalDeviceMemoryProperties memory_properties{};
54 vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties);
55 for (uint32_t i = 0; i < memory_properties.memoryTypeCount; ++i) {
56 if ((type_filter & (1U << i)) && (memory_properties.memoryTypes[i].propertyFlags & properties) == properties) {
65 validate_context(context);
71 VkBufferCreateInfo buffer_info{};
72 buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
73 buffer_info.size = size;
74 buffer_info.usage = usage;
75 buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
76 if (vkCreateBuffer(context.
device, &buffer_info,
nullptr, &buffer.
buffer) != VK_SUCCESS) {
80 VkMemoryRequirements requirements{};
81 vkGetBufferMemoryRequirements(context.
device, buffer.
buffer, &requirements);
83 VkMemoryAllocateInfo allocation{};
84 allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
85 allocation.allocationSize = requirements.size;
88 if (vkAllocateMemory(context.
device, &allocation,
nullptr, &buffer.
memory) != VK_SUCCESS) {
91 if (vkBindBufferMemory(context.
device, buffer.
buffer, buffer.
memory, 0) != VK_SUCCESS) {
102 if (device == VK_NULL_HANDLE) {
107 if (buffer.
buffer != VK_NULL_HANDLE) {
108 vkDestroyBuffer(device, buffer.
buffer,
nullptr);
110 if (buffer.
memory != VK_NULL_HANDLE) {
111 vkFreeMemory(device, buffer.
memory,
nullptr);
117 if (buffer.
mapped !=
nullptr) {
120 if (device == VK_NULL_HANDLE || buffer.
memory == VK_NULL_HANDLE || buffer.
size == 0) {
121 throw mxvk::Exception(
"map_buffer requires a valid buffer allocation");
123 if (vkMapMemory(device, buffer.
memory, 0, buffer.
size, 0, &buffer.
mapped) != VK_SUCCESS || buffer.
mapped ==
nullptr) {
129 if (device != VK_NULL_HANDLE && buffer.
memory != VK_NULL_HANDLE && buffer.
mapped !=
nullptr) {
130 vkUnmapMemory(device, buffer.
memory);
135 void create_image(
const VulkanContext &context, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &memory) {
136 validate_context(context);
137 if (width == 0 || height == 0) {
140 if (image != VK_NULL_HANDLE) {
141 vkDestroyImage(context.
device, image,
nullptr);
142 image = VK_NULL_HANDLE;
144 if (memory != VK_NULL_HANDLE) {
145 vkFreeMemory(context.
device, memory,
nullptr);
146 memory = VK_NULL_HANDLE;
149 VkImageCreateInfo image_info{};
150 image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
151 image_info.imageType = VK_IMAGE_TYPE_2D;
152 image_info.extent = {width, height, 1};
153 image_info.mipLevels = 1;
154 image_info.arrayLayers = 1;
155 image_info.format = format;
156 image_info.tiling = tiling;
157 image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
158 image_info.usage = usage;
159 image_info.samples = VK_SAMPLE_COUNT_1_BIT;
160 image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
161 if (vkCreateImage(context.
device, &image_info,
nullptr, &image) != VK_SUCCESS) {
165 VkMemoryRequirements requirements{};
166 vkGetImageMemoryRequirements(context.
device, image, &requirements);
168 VkMemoryAllocateInfo allocation{};
169 allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
170 allocation.allocationSize = requirements.size;
173 if (vkAllocateMemory(context.
device, &allocation,
nullptr, &memory) != VK_SUCCESS) {
176 if (vkBindImageMemory(context.
device, image, memory, 0) != VK_SUCCESS) {
180 if (memory != VK_NULL_HANDLE) {
181 vkFreeMemory(context.
device, memory,
nullptr);
182 memory = VK_NULL_HANDLE;
184 if (image != VK_NULL_HANDLE) {
185 vkDestroyImage(context.
device, image,
nullptr);
186 image = VK_NULL_HANDLE;
192 VkImageView
create_image_view(VkDevice device, VkImage image, VkFormat format, VkImageAspectFlags aspect) {
193 VkImageViewCreateInfo view_info{};
194 view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
195 view_info.image = image;
196 view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
197 view_info.format = format;
198 view_info.subresourceRange.aspectMask = aspect;
199 view_info.subresourceRange.baseMipLevel = 0;
200 view_info.subresourceRange.levelCount = 1;
201 view_info.subresourceRange.baseArrayLayer = 0;
202 view_info.subresourceRange.layerCount = 1;
204 VkImageView image_view = VK_NULL_HANDLE;
205 if (vkCreateImageView(device, &view_info,
nullptr, &image_view) != VK_SUCCESS) {
212 validate_upload_context(context);
213 VkCommandBufferAllocateInfo alloc_info{};
214 alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
215 alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
217 alloc_info.commandBufferCount = 1;
219 VkCommandBuffer command_buffer = VK_NULL_HANDLE;
220 if (vkAllocateCommandBuffers(context.
device, &alloc_info, &command_buffer) != VK_SUCCESS) {
224 VkCommandBufferBeginInfo begin_info{};
225 begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
226 begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
227 if (vkBeginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS) {
232 return command_buffer;
236 validate_upload_context(context);
237 if (vkEndCommandBuffer(command_buffer) != VK_SUCCESS) {
242 VkCommandBufferSubmitInfo command_buffer_info{};
243 command_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
244 command_buffer_info.commandBuffer = command_buffer;
246 VkSubmitInfo2 submit_info{};
247 submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
248 submit_info.commandBufferInfoCount = 1;
249 submit_info.pCommandBufferInfos = &command_buffer_info;
251 if (vkQueueSubmit2(context.
graphics_queue, 1, &submit_info, VK_NULL_HANDLE) != VK_SUCCESS) {
257 throw mxvk::Exception(
"failed to wait for one-time command completion");
271 VkBufferCopy2 copy_region{};
272 copy_region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
273 copy_region.size = size;
275 VkCopyBufferInfo2 copy_info{};
276 copy_info.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2;
277 copy_info.srcBuffer = source;
278 copy_info.dstBuffer = destination;
279 copy_info.regionCount = 1;
280 copy_info.pRegions = ©_region;
281 vkCmdCopyBuffer2(command_buffer, ©_info);
286 void transition_image_layout(VkCommandBuffer command_buffer, VkImage image, VkImageLayout old_layout, VkImageLayout new_layout, VkImageAspectFlags aspect) {
287 VkImageMemoryBarrier2 barrier{};
288 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
289 barrier.oldLayout = old_layout;
290 barrier.newLayout = new_layout;
291 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
292 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
293 barrier.image = image;
294 barrier.subresourceRange.aspectMask = aspect;
295 barrier.subresourceRange.baseMipLevel = 0;
296 barrier.subresourceRange.levelCount = 1;
297 barrier.subresourceRange.baseArrayLayer = 0;
298 barrier.subresourceRange.layerCount = 1;
301 if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED && new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
302 barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE;
303 barrier.srcAccessMask = VK_ACCESS_2_NONE;
304 barrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
305 barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
306 }
else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
307 barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
308 barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
309 barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
310 barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
315 VkDependencyInfo dependency{};
316 dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
317 dependency.imageMemoryBarrierCount = 1;
318 dependency.pImageMemoryBarriers = &barrier;
319 vkCmdPipelineBarrier2(command_buffer, &dependency);
322 void copy_buffer_to_image(VkCommandBuffer command_buffer, VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) {
323 VkBufferImageCopy2 region{};
324 region.sType = VK_STRUCTURE_TYPE_BUFFER_IMAGE_COPY_2;
325 region.bufferOffset = 0;
326 region.bufferRowLength = 0;
327 region.bufferImageHeight = 0;
328 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
329 region.imageSubresource.mipLevel = 0;
330 region.imageSubresource.baseArrayLayer = 0;
331 region.imageSubresource.layerCount = 1;
332 region.imageOffset = {0, 0, 0};
333 region.imageExtent = {width, height, 1};
335 VkCopyBufferToImageInfo2 copy_info{};
336 copy_info.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_TO_IMAGE_INFO_2;
337 copy_info.srcBuffer = buffer;
338 copy_info.dstImage = image;
339 copy_info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
340 copy_info.regionCount = 1;
341 copy_info.pRegions = ®ion;
342 vkCmdCopyBufferToImage2(command_buffer, ©_info);
346 validate_upload_context(context);
348 throw mxvk::Exception(
"create_texture_from_surface requires a valid SDL surface");
352 const uint32_t width =
static_cast<uint32_t
>(
surface->w);
353 const uint32_t height =
static_cast<uint32_t
>(
surface->h);
354 const VkDeviceSize image_size =
static_cast<VkDeviceSize
>(width) *
static_cast<VkDeviceSize
>(height) * 4U;
355 std::vector<std::byte> tight_pixels(
static_cast<size_t>(image_size));
358 const auto *src =
static_cast<const std::byte *
>(
surface->pixels);
359 auto *dst = tight_pixels.data();
360 const size_t tight_row_bytes =
static_cast<size_t>(width) * 4U;
361 for (uint32_t y = 0; y < height; ++y) {
362 std::memcpy(dst + y * tight_row_bytes, src +
static_cast<size_t>(y) *
static_cast<size_t>(
surface->pitch), tight_row_bytes);
367 create_buffer(context, image_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, staging);
369 std::memcpy(staging.
mapped, tight_pixels.data(), tight_pixels.size());
372 create_image(context, width, height, format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, texture.
image, texture.
memory);
383 create_sampler(context.
device, texture);
384 texture.
width = width;
394 std::unique_ptr<SDL_Surface,
decltype(&SDL_DestroySurface)>
surface(
mxvk::LoadPNG(path.c_str()), SDL_DestroySurface);
396 throw mxvk::Exception(std::format(
"failed to load texture PNG: {}", path));
402 if (device == VK_NULL_HANDLE) {
406 if (texture.
sampler != VK_NULL_HANDLE) {
407 vkDestroySampler(device, texture.
sampler,
nullptr);
409 if (texture.
view != VK_NULL_HANDLE) {
410 vkDestroyImageView(device, texture.
view,
nullptr);
412 if (texture.
image != VK_NULL_HANDLE) {
413 vkDestroyImage(device, texture.
image,
nullptr);
415 if (texture.
memory != VK_NULL_HANDLE) {
416 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.