MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_resource.cpp
Go to the documentation of this file.
2
4#include "mxvk/mxvk_png.hpp"
5
6#include <cstddef>
7#include <cstring>
8#include <format>
9#include <memory>
10#include <vector>
11
12namespace mxvk {
13
14 namespace {
15 void validate_context(const VulkanContext &context) {
16 if (context.device == VK_NULL_HANDLE || context.physical_device == VK_NULL_HANDLE) {
17 throw mxvk::Exception("mxvk resource helper requires a valid Vulkan device");
18 }
19 }
20
22 validate_context(context);
23 if (context.graphics_queue == VK_NULL_HANDLE || context.command_pool == VK_NULL_HANDLE) {
24 throw mxvk::Exception("mxvk upload helper requires a valid graphics queue and command pool");
25 }
26 }
27
28 void create_sampler(VkDevice device, TextureResource &texture) {
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");
44 }
45 }
46 } // namespace
47
48 uint32_t find_memory_type(VkPhysicalDevice physical_device,
49 uint32_t type_filter,
50 VkMemoryPropertyFlags properties) {
51 if (physical_device == VK_NULL_HANDLE) {
52 throw mxvk::Exception("find_memory_type requires a valid physical device");
53 }
54
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) {
60 return i;
61 }
62 }
63
64 throw mxvk::Exception("failed to find suitable Vulkan memory type");
65 }
66
67 void create_buffer(const VulkanContext &context,
68 VkDeviceSize size,
69 VkBufferUsageFlags usage,
70 VkMemoryPropertyFlags properties,
71 BufferResource &buffer) {
72 validate_context(context);
73 if (size == 0) {
74 throw mxvk::Exception("create_buffer requires a non-zero size");
75 }
76 destroy_buffer(context.device, buffer);
77
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) {
84 throw mxvk::Exception("failed to create Vulkan buffer");
85 }
86
87 VkMemoryRequirements requirements{};
88 vkGetBufferMemoryRequirements(context.device, buffer.buffer, &requirements);
89
90 VkMemoryAllocateInfo allocation{};
91 allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
92 allocation.allocationSize = requirements.size;
93 try {
94 allocation.memoryTypeIndex = find_memory_type(context.physical_device, requirements.memoryTypeBits, properties);
95 if (vkAllocateMemory(context.device, &allocation, nullptr, &buffer.memory) != VK_SUCCESS) {
96 throw mxvk::Exception("failed to allocate Vulkan buffer memory");
97 }
98 if (vkBindBufferMemory(context.device, buffer.buffer, buffer.memory, 0) != VK_SUCCESS) {
99 throw mxvk::Exception("failed to bind Vulkan buffer memory");
100 }
101 } catch (...) {
102 destroy_buffer(context.device, buffer);
103 throw;
104 }
105 buffer.size = size;
106 }
107
108 void destroy_buffer(VkDevice device, BufferResource &buffer) {
109 if (device == VK_NULL_HANDLE) {
110 buffer = {};
111 return;
112 }
113 unmap_buffer(device, buffer);
114 if (buffer.buffer != VK_NULL_HANDLE) {
115 vkDestroyBuffer(device, buffer.buffer, nullptr);
116 }
117 if (buffer.memory != VK_NULL_HANDLE) {
118 vkFreeMemory(device, buffer.memory, nullptr);
119 }
120 buffer = {};
121 }
122
123 void map_buffer(VkDevice device, BufferResource &buffer) {
124 if (buffer.mapped != nullptr) {
125 return;
126 }
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");
129 }
130 if (vkMapMemory(device, buffer.memory, 0, buffer.size, 0, &buffer.mapped) != VK_SUCCESS || buffer.mapped == nullptr) {
131 throw mxvk::Exception("failed to map Vulkan buffer memory");
132 }
133 }
134
135 void unmap_buffer(VkDevice device, BufferResource &buffer) {
136 if (device != VK_NULL_HANDLE && buffer.memory != VK_NULL_HANDLE && buffer.mapped != nullptr) {
137 vkUnmapMemory(device, buffer.memory);
138 }
139 buffer.mapped = nullptr;
140 }
141
142 void create_image(const VulkanContext &context,
143 uint32_t width,
144 uint32_t height,
145 VkFormat format,
146 VkImageTiling tiling,
147 VkImageUsageFlags usage,
148 VkMemoryPropertyFlags properties,
149 VkImage &image,
150 VkDeviceMemory &memory) {
151 validate_context(context);
152 if (width == 0 || height == 0) {
153 throw mxvk::Exception("create_image requires non-zero dimensions");
154 }
155 if (image != VK_NULL_HANDLE) {
156 vkDestroyImage(context.device, image, nullptr);
157 image = VK_NULL_HANDLE;
158 }
159 if (memory != VK_NULL_HANDLE) {
160 vkFreeMemory(context.device, memory, nullptr);
161 memory = VK_NULL_HANDLE;
162 }
163
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) {
177 throw mxvk::Exception("failed to create Vulkan image");
178 }
179
180 VkMemoryRequirements requirements{};
181 vkGetImageMemoryRequirements(context.device, image, &requirements);
182
183 VkMemoryAllocateInfo allocation{};
184 allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
185 allocation.allocationSize = requirements.size;
186 try {
187 allocation.memoryTypeIndex = find_memory_type(context.physical_device, requirements.memoryTypeBits, properties);
188 if (vkAllocateMemory(context.device, &allocation, nullptr, &memory) != VK_SUCCESS) {
189 throw mxvk::Exception("failed to allocate Vulkan image memory");
190 }
191 if (vkBindImageMemory(context.device, image, memory, 0) != VK_SUCCESS) {
192 throw mxvk::Exception("failed to bind Vulkan image memory");
193 }
194 } catch (...) {
195 if (memory != VK_NULL_HANDLE) {
196 vkFreeMemory(context.device, memory, nullptr);
197 memory = VK_NULL_HANDLE;
198 }
199 if (image != VK_NULL_HANDLE) {
200 vkDestroyImage(context.device, image, nullptr);
201 image = VK_NULL_HANDLE;
202 }
203 throw;
204 }
205 }
206
207 VkImageView create_image_view(VkDevice device,
208 VkImage image,
209 VkFormat format,
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;
221
222 VkImageView image_view = VK_NULL_HANDLE;
223 if (vkCreateImageView(device, &view_info, nullptr, &image_view) != VK_SUCCESS) {
224 throw mxvk::Exception("failed to create Vulkan image view");
225 }
226 return image_view;
227 }
228
229 VkCommandBuffer begin_one_time_commands(const VulkanContext &context) {
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;
234 alloc_info.commandPool = context.command_pool;
235 alloc_info.commandBufferCount = 1;
236
237 VkCommandBuffer command_buffer = VK_NULL_HANDLE;
238 if (vkAllocateCommandBuffers(context.device, &alloc_info, &command_buffer) != VK_SUCCESS) {
239 throw mxvk::Exception("failed to allocate one-time command buffer");
240 }
241
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) {
246 vkFreeCommandBuffers(context.device, context.command_pool, 1, &command_buffer);
247 throw mxvk::Exception("failed to begin one-time command buffer");
248 }
249
250 return command_buffer;
251 }
252
253 void end_one_time_commands(const VulkanContext &context, VkCommandBuffer command_buffer) {
254 validate_upload_context(context);
255 if (vkEndCommandBuffer(command_buffer) != VK_SUCCESS) {
256 vkFreeCommandBuffers(context.device, context.command_pool, 1, &command_buffer);
257 throw mxvk::Exception("failed to end one-time command buffer");
258 }
259
260 VkCommandBufferSubmitInfo command_buffer_info{};
261 command_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
262 command_buffer_info.commandBuffer = command_buffer;
263
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;
268
269 if (vkQueueSubmit2(context.graphics_queue, 1, &submit_info, VK_NULL_HANDLE) != VK_SUCCESS) {
270 vkFreeCommandBuffers(context.device, context.command_pool, 1, &command_buffer);
271 throw mxvk::Exception("failed to submit one-time command buffer");
272 }
273 if (vkQueueWaitIdle(context.graphics_queue) != VK_SUCCESS) {
274 vkFreeCommandBuffers(context.device, context.command_pool, 1, &command_buffer);
275 throw mxvk::Exception("failed to wait for one-time command completion");
276 }
277
278 // Upload command buffers are transient; callers only observe completion.
279 vkFreeCommandBuffers(context.device, context.command_pool, 1, &command_buffer);
280 }
281
282 void copy_buffer(const VulkanContext &context,
283 VkBuffer source,
284 VkBuffer destination,
285 VkDeviceSize size) {
286 if (size == 0) {
287 return;
288 }
289
290 VkCommandBuffer command_buffer = begin_one_time_commands(context);
291
292 VkBufferCopy2 copy_region{};
293 copy_region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
294 copy_region.size = size;
295
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 = &copy_region;
302 vkCmdCopyBuffer2(command_buffer, &copy_info);
303
304 end_one_time_commands(context, command_buffer);
305 }
306
307 void transition_image_layout(VkCommandBuffer command_buffer,
308 VkImage image,
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;
324
325 // MXVK requires Vulkan 1.3 synchronization2, so uploads use stage/access2 masks.
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;
336 } else {
337 throw mxvk::Exception("unsupported image layout transition");
338 }
339
340 VkDependencyInfo dependency{};
341 dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
342 dependency.imageMemoryBarrierCount = 1;
343 dependency.pImageMemoryBarriers = &barrier;
344 vkCmdPipelineBarrier2(command_buffer, &dependency);
345 }
346
347 void copy_buffer_to_image(VkCommandBuffer command_buffer,
348 VkBuffer buffer,
349 VkImage image,
350 uint32_t width,
351 uint32_t height) {
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};
363
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 = &region;
371 vkCmdCopyBufferToImage2(command_buffer, &copy_info);
372 }
373
375 SDL_Surface *surface,
376 TextureResource &texture,
377 VkFormat format) {
378 validate_upload_context(context);
379 if (surface == nullptr || surface->pixels == nullptr || surface->w <= 0 || surface->h <= 0) {
380 throw mxvk::Exception("create_texture_from_surface requires a valid SDL surface");
381 }
382 destroy_texture(context.device, texture);
383
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));
388
389 // SDL surfaces may have padded rows; Vulkan buffer-image copies use tight rows here.
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);
395 }
396
397 BufferResource staging{};
398 try {
399 create_buffer(context,
400 image_size,
401 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
402 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
403 staging);
404 map_buffer(context.device, staging);
405 std::memcpy(staging.mapped, tight_pixels.data(), tight_pixels.size());
406 unmap_buffer(context.device, staging);
407
408 create_image(context,
409 width,
410 height,
411 format,
412 VK_IMAGE_TILING_OPTIMAL,
413 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
414 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
415 texture.image,
416 texture.memory);
417
418 const VkCommandBuffer cmd = begin_one_time_commands(context);
419 transition_image_layout(cmd, texture.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
420 copy_buffer_to_image(cmd, staging.buffer, texture.image, width, height);
421 transition_image_layout(cmd, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
422 end_one_time_commands(context, cmd);
423
424 destroy_buffer(context.device, staging);
425
426 texture.view = create_image_view(context.device, texture.image, format, VK_IMAGE_ASPECT_COLOR_BIT);
427 create_sampler(context.device, texture);
428 texture.width = width;
429 texture.height = height;
430 } catch (...) {
431 destroy_buffer(context.device, staging);
432 destroy_texture(context.device, texture);
433 throw;
434 }
435 }
436
438 const std::string &path,
439 TextureResource &texture,
440 VkFormat format) {
441 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> surface(mxvk::LoadPNG(path.c_str()), SDL_DestroySurface);
442 if (surface == nullptr) {
443 throw mxvk::Exception(std::format("failed to load texture PNG: {}", path));
444 }
445 create_texture_from_surface(context, surface.get(), texture, format);
446 }
447
448 void destroy_texture(VkDevice device, TextureResource &texture) {
449 if (device == VK_NULL_HANDLE) {
450 texture = {};
451 return;
452 }
453 if (texture.sampler != VK_NULL_HANDLE) {
454 vkDestroySampler(device, texture.sampler, nullptr);
455 }
456 if (texture.view != VK_NULL_HANDLE) {
457 vkDestroyImageView(device, texture.view, nullptr);
458 }
459 if (texture.image != VK_NULL_HANDLE) {
460 vkDestroyImage(device, texture.image, nullptr);
461 }
462 if (texture.memory != VK_NULL_HANDLE) {
463 vkFreeMemory(device, texture.memory, nullptr);
464 }
465 texture = {};
466 }
467
468} // namespace mxvk
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.
Definition mxvk.hpp:30
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.
Definition mxvk_png.cpp:103
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.