MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_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, 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");
51 }
52
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) {
57 return i;
58 }
59 }
60
61 throw mxvk::Exception("failed to find suitable Vulkan memory type");
62 }
63
64 void create_buffer(const VulkanContext &context, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, BufferResource &buffer) {
65 validate_context(context);
66 if (size == 0) {
67 throw mxvk::Exception("create_buffer requires a non-zero size");
68 }
69 destroy_buffer(context.device, buffer);
70
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) {
77 throw mxvk::Exception("failed to create Vulkan buffer");
78 }
79
80 VkMemoryRequirements requirements{};
81 vkGetBufferMemoryRequirements(context.device, buffer.buffer, &requirements);
82
83 VkMemoryAllocateInfo allocation{};
84 allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
85 allocation.allocationSize = requirements.size;
86 try {
87 allocation.memoryTypeIndex = find_memory_type(context.physical_device, requirements.memoryTypeBits, properties);
88 if (vkAllocateMemory(context.device, &allocation, nullptr, &buffer.memory) != VK_SUCCESS) {
89 throw mxvk::Exception("failed to allocate Vulkan buffer memory");
90 }
91 if (vkBindBufferMemory(context.device, buffer.buffer, buffer.memory, 0) != VK_SUCCESS) {
92 throw mxvk::Exception("failed to bind Vulkan buffer memory");
93 }
94 } catch (...) {
95 destroy_buffer(context.device, buffer);
96 throw;
97 }
98 buffer.size = size;
99 }
100
101 void destroy_buffer(VkDevice device, BufferResource &buffer) {
102 if (device == VK_NULL_HANDLE) {
103 buffer = {};
104 return;
105 }
106 unmap_buffer(device, buffer);
107 if (buffer.buffer != VK_NULL_HANDLE) {
108 vkDestroyBuffer(device, buffer.buffer, nullptr);
109 }
110 if (buffer.memory != VK_NULL_HANDLE) {
111 vkFreeMemory(device, buffer.memory, nullptr);
112 }
113 buffer = {};
114 }
115
116 void map_buffer(VkDevice device, BufferResource &buffer) {
117 if (buffer.mapped != nullptr) {
118 return;
119 }
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");
122 }
123 if (vkMapMemory(device, buffer.memory, 0, buffer.size, 0, &buffer.mapped) != VK_SUCCESS || buffer.mapped == nullptr) {
124 throw mxvk::Exception("failed to map Vulkan buffer memory");
125 }
126 }
127
128 void unmap_buffer(VkDevice device, BufferResource &buffer) {
129 if (device != VK_NULL_HANDLE && buffer.memory != VK_NULL_HANDLE && buffer.mapped != nullptr) {
130 vkUnmapMemory(device, buffer.memory);
131 }
132 buffer.mapped = nullptr;
133 }
134
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) {
138 throw mxvk::Exception("create_image requires non-zero dimensions");
139 }
140 if (image != VK_NULL_HANDLE) {
141 vkDestroyImage(context.device, image, nullptr);
142 image = VK_NULL_HANDLE;
143 }
144 if (memory != VK_NULL_HANDLE) {
145 vkFreeMemory(context.device, memory, nullptr);
146 memory = VK_NULL_HANDLE;
147 }
148
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) {
162 throw mxvk::Exception("failed to create Vulkan image");
163 }
164
165 VkMemoryRequirements requirements{};
166 vkGetImageMemoryRequirements(context.device, image, &requirements);
167
168 VkMemoryAllocateInfo allocation{};
169 allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
170 allocation.allocationSize = requirements.size;
171 try {
172 allocation.memoryTypeIndex = find_memory_type(context.physical_device, requirements.memoryTypeBits, properties);
173 if (vkAllocateMemory(context.device, &allocation, nullptr, &memory) != VK_SUCCESS) {
174 throw mxvk::Exception("failed to allocate Vulkan image memory");
175 }
176 if (vkBindImageMemory(context.device, image, memory, 0) != VK_SUCCESS) {
177 throw mxvk::Exception("failed to bind Vulkan image memory");
178 }
179 } catch (...) {
180 if (memory != VK_NULL_HANDLE) {
181 vkFreeMemory(context.device, memory, nullptr);
182 memory = VK_NULL_HANDLE;
183 }
184 if (image != VK_NULL_HANDLE) {
185 vkDestroyImage(context.device, image, nullptr);
186 image = VK_NULL_HANDLE;
187 }
188 throw;
189 }
190 }
191
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;
203
204 VkImageView image_view = VK_NULL_HANDLE;
205 if (vkCreateImageView(device, &view_info, nullptr, &image_view) != VK_SUCCESS) {
206 throw mxvk::Exception("failed to create Vulkan image view");
207 }
208 return image_view;
209 }
210
211 VkCommandBuffer begin_one_time_commands(const VulkanContext &context) {
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;
216 alloc_info.commandPool = context.command_pool;
217 alloc_info.commandBufferCount = 1;
218
219 VkCommandBuffer command_buffer = VK_NULL_HANDLE;
220 if (vkAllocateCommandBuffers(context.device, &alloc_info, &command_buffer) != VK_SUCCESS) {
221 throw mxvk::Exception("failed to allocate one-time command buffer");
222 }
223
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) {
228 vkFreeCommandBuffers(context.device, context.command_pool, 1, &command_buffer);
229 throw mxvk::Exception("failed to begin one-time command buffer");
230 }
231
232 return command_buffer;
233 }
234
235 void end_one_time_commands(const VulkanContext &context, VkCommandBuffer command_buffer) {
236 validate_upload_context(context);
237 if (vkEndCommandBuffer(command_buffer) != VK_SUCCESS) {
238 vkFreeCommandBuffers(context.device, context.command_pool, 1, &command_buffer);
239 throw mxvk::Exception("failed to end one-time command buffer");
240 }
241
242 VkCommandBufferSubmitInfo command_buffer_info{};
243 command_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
244 command_buffer_info.commandBuffer = command_buffer;
245
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;
250
251 if (vkQueueSubmit2(context.graphics_queue, 1, &submit_info, VK_NULL_HANDLE) != VK_SUCCESS) {
252 vkFreeCommandBuffers(context.device, context.command_pool, 1, &command_buffer);
253 throw mxvk::Exception("failed to submit one-time command buffer");
254 }
255 if (vkQueueWaitIdle(context.graphics_queue) != VK_SUCCESS) {
256 vkFreeCommandBuffers(context.device, context.command_pool, 1, &command_buffer);
257 throw mxvk::Exception("failed to wait for one-time command completion");
258 }
259
260 // Upload command buffers are transient; callers only observe completion.
261 vkFreeCommandBuffers(context.device, context.command_pool, 1, &command_buffer);
262 }
263
264 void copy_buffer(const VulkanContext &context, VkBuffer source, VkBuffer destination, VkDeviceSize size) {
265 if (size == 0) {
266 return;
267 }
268
269 VkCommandBuffer command_buffer = begin_one_time_commands(context);
270
271 VkBufferCopy2 copy_region{};
272 copy_region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
273 copy_region.size = size;
274
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 = &copy_region;
281 vkCmdCopyBuffer2(command_buffer, &copy_info);
282
283 end_one_time_commands(context, command_buffer);
284 }
285
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;
299
300 // MXVK requires Vulkan 1.3 synchronization2, so uploads use stage/access2 masks.
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;
311 } else {
312 throw mxvk::Exception("unsupported image layout transition");
313 }
314
315 VkDependencyInfo dependency{};
316 dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
317 dependency.imageMemoryBarrierCount = 1;
318 dependency.pImageMemoryBarriers = &barrier;
319 vkCmdPipelineBarrier2(command_buffer, &dependency);
320 }
321
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};
334
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 = &region;
342 vkCmdCopyBufferToImage2(command_buffer, &copy_info);
343 }
344
345 void create_texture_from_surface(const VulkanContext &context, SDL_Surface *surface, TextureResource &texture, VkFormat format) {
346 validate_upload_context(context);
347 if (surface == nullptr || surface->pixels == nullptr || surface->w <= 0 || surface->h <= 0) {
348 throw mxvk::Exception("create_texture_from_surface requires a valid SDL surface");
349 }
350 destroy_texture(context.device, texture);
351
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));
356
357 // SDL surfaces may have padded rows; Vulkan buffer-image copies use tight rows here.
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);
363 }
364
365 BufferResource staging{};
366 try {
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);
368 map_buffer(context.device, staging);
369 std::memcpy(staging.mapped, tight_pixels.data(), tight_pixels.size());
370 unmap_buffer(context.device, staging);
371
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);
373
374 const VkCommandBuffer cmd = begin_one_time_commands(context);
375 transition_image_layout(cmd, texture.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
376 copy_buffer_to_image(cmd, staging.buffer, texture.image, width, height);
377 transition_image_layout(cmd, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
378 end_one_time_commands(context, cmd);
379
380 destroy_buffer(context.device, staging);
381
382 texture.view = create_image_view(context.device, texture.image, format, VK_IMAGE_ASPECT_COLOR_BIT);
383 create_sampler(context.device, texture);
384 texture.width = width;
385 texture.height = height;
386 } catch (...) {
387 destroy_buffer(context.device, staging);
388 destroy_texture(context.device, texture);
389 throw;
390 }
391 }
392
393 void create_texture_from_png(const VulkanContext &context, const std::string &path, TextureResource &texture, VkFormat format) {
394 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> surface(mxvk::LoadPNG(path.c_str()), SDL_DestroySurface);
395 if (surface == nullptr) {
396 throw mxvk::Exception(std::format("failed to load texture PNG: {}", path));
397 }
398 create_texture_from_surface(context, surface.get(), texture, format);
399 }
400
401 void destroy_texture(VkDevice device, TextureResource &texture) {
402 if (device == VK_NULL_HANDLE) {
403 texture = {};
404 return;
405 }
406 if (texture.sampler != VK_NULL_HANDLE) {
407 vkDestroySampler(device, texture.sampler, nullptr);
408 }
409 if (texture.view != VK_NULL_HANDLE) {
410 vkDestroyImageView(device, texture.view, nullptr);
411 }
412 if (texture.image != VK_NULL_HANDLE) {
413 vkDestroyImage(device, texture.image, nullptr);
414 }
415 if (texture.memory != VK_NULL_HANDLE) {
416 vkFreeMemory(device, texture.memory, nullptr);
417 }
418 texture = {};
419 }
420
421} // 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:31
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:89
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.