perf(buffer): capacity-reuse in VulkanBuffer::Resize (#63)
Resize previously always destroyed + reallocated. It now reuses the existing allocation in place when the new request still fits the created capacity and the immutable-at-create properties match: usage flags are fixed at create, and the chosen memory type must still satisfy the required property flags. preferredPropertyFlags is a best-effort perf hint and is intentionally not part of the guard. On reuse the buffer handle, device address and mapped pointer are preserved; only `size` shrinks to the new logical extent. Tracks capacity and the created usage flags on VulkanBufferBase, set at Create and carried through the move constructor. Adds VulkanBufferResizeReuse, a device-free regression test that drives the reuse guard directly and asserts the in-place path issues no Vulkan call. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ab222ffa1f
commit
b6d0ec5cae
3 changed files with 189 additions and 0 deletions
|
|
@ -40,6 +40,12 @@ namespace Crafter {
|
|||
// COHERENT bit on a coherent type (and vice versa), so the flush/
|
||||
// invalidate paths gate on this recorded value, never on the request.
|
||||
VkMemoryPropertyFlags memoryPropertyFlagsChosen = 0;
|
||||
// Byte capacity the buffer was created with, and the usage flags it was
|
||||
// created with. Resize reuses the allocation in place when a new
|
||||
// request still fits within `capacity` and these immutable-at-create
|
||||
// properties match, avoiding a destroy+reallocate.
|
||||
std::uint32_t capacity = 0;
|
||||
VkBufferUsageFlags2 usageFlagsCreated = 0;
|
||||
};
|
||||
|
||||
export template<typename T>
|
||||
|
|
@ -67,6 +73,8 @@ namespace Crafter {
|
|||
// available on every device — see Device::GetMemoryType.
|
||||
void Create(VkBufferUsageFlags2 usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, std::uint32_t count, VkMemoryPropertyFlags preferredPropertyFlags = 0) {
|
||||
size = count * sizeof(T);
|
||||
capacity = size;
|
||||
usageFlagsCreated = usageFlags;
|
||||
|
||||
// Carry usage in the maintenance5 flags2 chain so 64-bit bits
|
||||
// (e.g. VK_BUFFER_USAGE_2_MEMORY_DECOMPRESSION_BIT_EXT, bit 35)
|
||||
|
|
@ -122,6 +130,22 @@ namespace Crafter {
|
|||
}
|
||||
|
||||
void Resize(VkBufferUsageFlags2 usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, std::uint32_t count, VkMemoryPropertyFlags preferredPropertyFlags = 0) {
|
||||
// Reuse the existing allocation in place when the request still fits
|
||||
// and the fixed-at-create properties match: usage flags are
|
||||
// immutable after creation, and the memory type already chosen must
|
||||
// still satisfy the required property flags. preferredPropertyFlags
|
||||
// is a best-effort perf hint and does not affect correctness, so it
|
||||
// is intentionally not part of the guard. The buffer handle (and its
|
||||
// device address / mapped pointer) is preserved, only `size` shrinks
|
||||
// to the new logical extent.
|
||||
std::uint32_t requestedSize = count * sizeof(T);
|
||||
if(buffer != VK_NULL_HANDLE
|
||||
&& requestedSize <= capacity
|
||||
&& usageFlags == usageFlagsCreated
|
||||
&& (memoryPropertyFlagsChosen & memoryPropertyFlags) == memoryPropertyFlags) {
|
||||
size = requestedSize;
|
||||
return;
|
||||
}
|
||||
if(buffer != VK_NULL_HANDLE) {
|
||||
Clear();
|
||||
}
|
||||
|
|
@ -227,6 +251,8 @@ namespace Crafter {
|
|||
buffer = other.buffer;
|
||||
memory = other.memory;
|
||||
size = other.size;
|
||||
capacity = other.capacity;
|
||||
usageFlagsCreated = other.usageFlagsCreated;
|
||||
memoryPropertyFlagsChosen = other.memoryPropertyFlagsChosen;
|
||||
other.buffer = VK_NULL_HANDLE;
|
||||
address = other.address;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue