The open source OpenXR runtime

c/render: Add simple sub-allocation code

+204
+1
src/xrt/compositor/CMakeLists.txt
··· 122 122 render/render_interface.h 123 123 render/render_resources.c 124 124 render/render_shaders.c 125 + render/render_sub_alloc.c 125 126 render/render_util.c 126 127 ) 127 128 # The aux_vk library needs to be public to include Vulkan.
+99
src/xrt/compositor/render/render_interface.h
··· 41 41 */ 42 42 43 43 /*! 44 + * The value `minUniformBufferOffsetAlignment` is defined by the Vulkan spec as 45 + * having a max value of 256. Use this value to safely figure out sizes and 46 + * alignment of UBO sub-allocation. It is also the max for 'nonCoherentAtomSize` 47 + * which if we need to do flushing is what we need to align UBOs to. 48 + * 49 + * https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceLimits.html 50 + * https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#limits-minmax 51 + */ 52 + #define RENDER_ALWAYS_SAFE_UBO_ALIGNMENT (256) 53 + 54 + /*! 44 55 * Max number of layers for layer squasher, can be different from 45 56 * @ref COMP_MAX_LAYERS as the render module is separate from the compositor. 46 57 */ ··· 226 237 */ 227 238 VkResult 228 239 render_buffer_write(struct vk_bundle *vk, struct render_buffer *buffer, void *data, VkDeviceSize size); 240 + 241 + 242 + /* 243 + * 244 + * Sub-alloc. 245 + * 246 + */ 247 + 248 + /*! 249 + * Per frame sub-allocation into a buffer, used to reduce the number of UBO 250 + * objects we need to create. There is no way to free a sub-allocation, this is 251 + * done implicitly at the end of the frame when @ref render_sub_alloc_tracker is 252 + * zeroed out. 253 + * 254 + * @see render_sub_alloc_tracker 255 + */ 256 + struct render_sub_alloc 257 + { 258 + /*! 259 + * The buffer this is allocated from, it's the callers responsibility 260 + * to keep it alive for as long as the sub-allocation is used. 261 + */ 262 + VkBuffer buffer; 263 + 264 + //! Size of sub-allocation. 265 + VkDeviceSize size; 266 + 267 + //! Offset into buffer. 268 + VkDeviceSize offset; 269 + }; 270 + 271 + /*! 272 + * A per frame tracker of sub-allocation out of a buffer, used to reduce the 273 + * number of UBO objects we need to create. This code is designed with one 274 + * constraint in mind, that the lifetime of a sub-allocation is only for one 275 + * frame and is discarded at the end of it, but also alive for the entire frame. 276 + * This removes the need to free indivudial sub-allocation, or even track them 277 + * beyond filling the UBO data and descriptor sets. 278 + * 279 + * @see render_sub_alloc 280 + */ 281 + struct render_sub_alloc_tracker 282 + { 283 + /*! 284 + * The buffer to allocate from, it's the callers responsibility to keep 285 + * it alive for as long as the sub-allocations are in used. 286 + */ 287 + VkBuffer buffer; 288 + 289 + //! Start of memory, if buffer was mapped with initialised. 290 + void *mapped; 291 + 292 + //! Total size of buffer. 293 + VkDeviceSize total_size; 294 + 295 + //! Currently used memory. 296 + VkDeviceSize used; 297 + }; 298 + 299 + /*! 300 + * Init a @ref render_sub_alloc_tracker struct from a @ref render_buffer, the 301 + * caller is responsible for keeping @p buffer alive while the sub allocator 302 + * is being used. 303 + */ 304 + void 305 + render_sub_alloc_tracker_init(struct render_sub_alloc_tracker *rsat, struct render_buffer *buffer); 306 + 307 + /*! 308 + * Allocate enough memory (with constraints of UBOs) of @p size, return the 309 + * pointer to the mapped memory or null if the buffer wasn't allocated. 310 + */ 311 + XRT_CHECK_RESULT VkResult 312 + render_sub_alloc_ubo_alloc_and_get_ptr(struct vk_bundle *vk, 313 + struct render_sub_alloc_tracker *rsat, 314 + VkDeviceSize size, 315 + void **out_ptr, 316 + struct render_sub_alloc *out_rsa); 317 + 318 + /*! 319 + * Allocate enough memory (with constraints of UBOs) to hold the memory in @ptr 320 + * and copy that memory to the buffer using the CPU. 321 + */ 322 + XRT_CHECK_RESULT VkResult 323 + render_sub_alloc_ubo_alloc_and_write(struct vk_bundle *vk, 324 + struct render_sub_alloc_tracker *rsat, 325 + const void *ptr, 326 + VkDeviceSize size, 327 + struct render_sub_alloc *out_rsa); 229 328 230 329 231 330 /*
+104
src/xrt/compositor/render/render_sub_alloc.c
··· 1 + // Copyright 2019-2023, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief Sub allocation functions. 6 + * @author Jakob Bornecrantz <jakob@collabora.com> 7 + * @ingroup comp_render 8 + */ 9 + 10 + #include "vk/vk_mini_helpers.h" 11 + #include "render/render_interface.h" 12 + 13 + 14 + // Align a size with a power of two value. 15 + static VkDeviceSize 16 + align_padding_pot(VkDeviceSize size, VkDeviceSize alignment) 17 + { 18 + return (size + alignment - 1) & ~(alignment - 1); 19 + } 20 + 21 + 22 + /* 23 + * 24 + * 'Exported' functions. 25 + * 26 + */ 27 + 28 + void 29 + render_sub_alloc_tracker_init(struct render_sub_alloc_tracker *rsat, struct render_buffer *buffer) 30 + { 31 + rsat->buffer = buffer->buffer; 32 + rsat->used = 0; 33 + rsat->total_size = buffer->size; 34 + rsat->mapped = buffer->mapped; 35 + } 36 + 37 + XRT_CHECK_RESULT VkResult 38 + render_sub_alloc_ubo_alloc_and_get_ptr(struct vk_bundle *vk, 39 + struct render_sub_alloc_tracker *rsat, 40 + VkDeviceSize size, 41 + void **out_ptr, 42 + struct render_sub_alloc *out_rsa) 43 + { 44 + assert(rsat->total_size >= rsat->used); 45 + VkDeviceSize space_left = rsat->total_size - rsat->used; 46 + 47 + if (space_left < size) { 48 + VK_ERROR(vk, "Can not fit %u in left %u of total %u", (uint32_t)size, (uint32_t)space_left, 49 + (uint32_t)rsat->total_size); 50 + return VK_ERROR_OUT_OF_DEVICE_MEMORY; 51 + } 52 + 53 + // Make sure we align from start of memory. 54 + VkDeviceSize padded_used = align_padding_pot(rsat->used + size, RENDER_ALWAYS_SAFE_UBO_ALIGNMENT); 55 + 56 + // Save the current used as offset. 57 + VkDeviceSize offset = rsat->used; 58 + 59 + // Ensure used never gets larger then total_size. 60 + if (padded_used > rsat->total_size) { 61 + rsat->used = rsat->total_size; 62 + } else { 63 + rsat->used = padded_used; 64 + } 65 + 66 + void *ptr = rsat->mapped == NULL ? NULL : (void *)((uint8_t *)rsat->mapped + offset); 67 + 68 + 69 + /* 70 + * All done. 71 + */ 72 + 73 + *out_ptr = ptr; 74 + *out_rsa = (struct render_sub_alloc){ 75 + .buffer = rsat->buffer, 76 + .size = size, 77 + .offset = offset, 78 + }; 79 + 80 + return VK_SUCCESS; 81 + } 82 + 83 + XRT_CHECK_RESULT VkResult 84 + render_sub_alloc_ubo_alloc_and_write(struct vk_bundle *vk, 85 + struct render_sub_alloc_tracker *rsat, 86 + const void *src, 87 + VkDeviceSize size, 88 + struct render_sub_alloc *out_rsa) 89 + { 90 + VkResult ret; 91 + 92 + if (rsat->mapped == NULL) { 93 + VK_ERROR(vk, "Sub allocation not mapped"); 94 + return VK_ERROR_OUT_OF_DEVICE_MEMORY; 95 + } 96 + 97 + void *dst; 98 + ret = render_sub_alloc_ubo_alloc_and_get_ptr(vk, rsat, size, &dst, out_rsa); 99 + VK_CHK_AND_RET(ret, "render_sub_alloc_ubo_alloc_and_get_ptr"); 100 + 101 + memcpy(dst, src, size); 102 + 103 + return VK_SUCCESS; 104 + }