Merge pull request 'perf(ui): flush only the written descriptor range, not the whole heap (#61)' (#99) from claude/issue-61 into master
This commit is contained in:
commit
c3d7f52891
6 changed files with 236 additions and 7 deletions
|
|
@ -161,6 +161,14 @@ export namespace Crafter {
|
|||
|
||||
inline static VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||
|
||||
// VkPhysicalDeviceLimits::nonCoherentAtomSize — the alignment (in bytes,
|
||||
// a power of two) that vkFlushMappedMemoryRanges / vkInvalidateMappedMemoryRanges
|
||||
// require for the offset and size of a sub-buffer range. Whole-buffer
|
||||
// (VK_WHOLE_SIZE) flushes sidestep it; ranged flushes must round outward
|
||||
// to it (see VulkanBuffer::FlushDevice(offset,bytes)). Populated at device
|
||||
// creation; defaults to 1 so the rounding math is well-defined before then.
|
||||
inline static VkDeviceSize nonCoherentAtomSize = 1;
|
||||
|
||||
// Core physical-device properties, captured once at Initialize from the
|
||||
// VkPhysicalDeviceProperties2 query. Kept because the pipeline-cache
|
||||
// persistence path needs vendorID / deviceID / pipelineCacheUUID to
|
||||
|
|
|
|||
|
|
@ -29,10 +29,35 @@ import std;
|
|||
import :Device;
|
||||
|
||||
namespace Crafter {
|
||||
// Round a host-write flush range outward to nonCoherentAtomSize boundaries —
|
||||
// the alignment vkFlushMappedMemoryRanges demands for a sub-buffer range
|
||||
// (whole-buffer VK_WHOLE_SIZE calls sidestep it). `atom` is
|
||||
// VkPhysicalDeviceLimits::nonCoherentAtomSize (a power of two ≥ 1) and
|
||||
// `mappingSize` is the byte size the memory was mapped with (the allocation
|
||||
// size). The start rounds down and the end rounds up to atom multiples; the
|
||||
// end is then clamped to `mappingSize`. Clamping is what keeps the range
|
||||
// valid even when `mappingSize` itself is not atom-aligned: the spec permits
|
||||
// a non-atom-multiple size only when offset+size equals the mapping size, and
|
||||
// the clamp lands exactly on that exception. Pure math, so it is unit-tested
|
||||
// without a device.
|
||||
export struct MappedFlushRange { VkDeviceSize offset; VkDeviceSize size; };
|
||||
export constexpr MappedFlushRange AlignMappedFlushRange(
|
||||
VkDeviceSize offset, VkDeviceSize bytes,
|
||||
VkDeviceSize atom, VkDeviceSize mappingSize) {
|
||||
VkDeviceSize begin = (offset / atom) * atom;
|
||||
VkDeviceSize end = ((offset + bytes + atom - 1) / atom) * atom;
|
||||
if (end > mappingSize) end = mappingSize;
|
||||
return { begin, end - begin };
|
||||
}
|
||||
|
||||
export class VulkanBufferBase {
|
||||
public:
|
||||
VkDeviceAddress address;
|
||||
std::uint32_t size;
|
||||
// Byte size the memory was mapped with — equal to the allocation's
|
||||
// VkMemoryRequirements::size, which is ≥ `size` and what ranged flushes
|
||||
// clamp their upper bound to. Only meaningful for mapped buffers.
|
||||
VkDeviceSize mappedSize = 0;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory;
|
||||
// Property flags of the memory type actually chosen by GetMemoryType —
|
||||
|
|
@ -116,6 +141,7 @@ namespace Crafter {
|
|||
address = vkGetBufferDeviceAddress(Device::device, &addressInfo);
|
||||
|
||||
if constexpr(Mapped) {
|
||||
mappedSize = memReqs.size;
|
||||
Device::CheckVkResult(vkMapMemory(Device::device, memory, 0, memReqs.size, 0, reinterpret_cast<void**>(&(VulkanBufferMappedConditional<T, true>::value))));
|
||||
}
|
||||
}
|
||||
|
|
@ -208,6 +234,27 @@ namespace Crafter {
|
|||
vkFlushMappedMemoryRanges(Device::device, 1, &range);
|
||||
}
|
||||
|
||||
// Flush only the host writes in [offset, offset+bytes) to the device,
|
||||
// instead of the whole buffer. Use after touching a small sub-range
|
||||
// (e.g. one descriptor in a multi-KB descriptor heap) so cache
|
||||
// maintenance scales with the bytes actually written. The range is
|
||||
// rounded outward to nonCoherentAtomSize as the Vulkan spec requires.
|
||||
// No-op on coherent memory, same gate as the whole-buffer FlushDevice().
|
||||
void FlushDevice(VkDeviceSize offset, VkDeviceSize bytes) requires(Mapped) {
|
||||
if (memoryPropertyFlagsChosen & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) {
|
||||
return;
|
||||
}
|
||||
MappedFlushRange r = AlignMappedFlushRange(
|
||||
offset, bytes, Device::nonCoherentAtomSize, mappedSize);
|
||||
VkMappedMemoryRange range = {
|
||||
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
||||
.memory = memory,
|
||||
.offset = r.offset,
|
||||
.size = r.size
|
||||
};
|
||||
vkFlushMappedMemoryRanges(Device::device, 1, &range);
|
||||
}
|
||||
|
||||
void FlushDevice(VkCommandBuffer cmd, VkAccessFlags dstAccessMask, VkPipelineStageFlags dstStageMask) requires(Mapped) {
|
||||
FlushDevice();
|
||||
VkBufferMemoryBarrier barrier = {
|
||||
|
|
@ -251,6 +298,7 @@ namespace Crafter {
|
|||
buffer = other.buffer;
|
||||
memory = other.memory;
|
||||
size = other.size;
|
||||
mappedSize = other.mappedSize;
|
||||
capacity = other.capacity;
|
||||
usageFlagsCreated = other.usageFlagsCreated;
|
||||
memoryPropertyFlagsChosen = other.memoryPropertyFlagsChosen;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue