feat(device): runtime upload-strategy helper PreferDirectDeviceWrite (#89)
Add Device::PreferDirectDeviceWrite(size) so the memory-placement cluster
(#65/#72/#73/#75) no longer re-derives where a CPU-written, GPU-read buffer
should live. It picks the strategy at runtime from Device::memoryProperties:
- No DEVICE_LOCAL|HOST_VISIBLE type (no resizable BAR) -> false (must stage).
- ReBAR/UMA (BAR heap >= 90% of largest DEVICE_LOCAL heap) -> true: map and
write directly; a staging copy would be pure overhead.
- Small BAR window (BAR heap << VRAM) -> true only for buffers within a
per-window budget (1/8 of the window); large buffers stage so they don't
exhaust the window (#58).
Complements GetMemoryType (#59): #59 picks the memory *type*, this picks the
*strategy*. Upload-only — readback (#74) wants HOST_CACHED and stays separate.
A VK_EXT_memory_budget-driven remaining-space check is noted as a follow-up.
Tested by tests/UploadStrategy (pure CPU over synthetic ReBAR / UMA /
small-window / no-BAR layouts, no GPU device needed), mirroring
MemoryTypeFallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a19c2934c8
commit
cfbe181d9e
4 changed files with 275 additions and 0 deletions
|
|
@ -840,4 +840,49 @@ std::uint32_t Device::GetMemoryType(uint32_t typeBits, VkMemoryPropertyFlags req
|
|||
}
|
||||
|
||||
throw std::runtime_error("Could not find a matching memory type");
|
||||
}
|
||||
|
||||
bool Device::PreferDirectDeviceWrite(VkDeviceSize size) {
|
||||
constexpr VkMemoryPropertyFlags directFlags =
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
|
||||
|
||||
// 1. Find a DEVICE_LOCAL | HOST_VISIBLE type and remember its heap. Without
|
||||
// one (no resizable BAR), direct writes are impossible -> must stage.
|
||||
bool haveDirectType = false;
|
||||
std::uint32_t barHeapIndex = 0;
|
||||
for (std::uint32_t i = 0; i < memoryProperties.memoryTypeCount; ++i) {
|
||||
if ((memoryProperties.memoryTypes[i].propertyFlags & directFlags) == directFlags) {
|
||||
haveDirectType = true;
|
||||
barHeapIndex = memoryProperties.memoryTypes[i].heapIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!haveDirectType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Compare the host-visible device-local heap against the largest plain
|
||||
// DEVICE_LOCAL heap (the true VRAM size).
|
||||
VkDeviceSize barHeap = memoryProperties.memoryHeaps[barHeapIndex].size;
|
||||
VkDeviceSize vram = 0;
|
||||
for (std::uint32_t i = 0; i < memoryProperties.memoryHeapCount; ++i) {
|
||||
if (memoryProperties.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) {
|
||||
vram = std::max(vram, memoryProperties.memoryHeaps[i].size);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. ReBAR / UMA: the BAR heap covers (almost) all of VRAM -> direct.
|
||||
// barHeap >= 0.9 * vram, written to dodge floating point / overflow.
|
||||
if (barHeap >= vram - vram / 10) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. Small BAR window: direct only for buffers small enough that several
|
||||
// still fit the window — large buffers stage so they don't exhaust it.
|
||||
// The budget is tied to the actual window size rather than a magic
|
||||
// constant so it adapts across devices (a fixed cap would be wrong for
|
||||
// both a 256 MiB and a 4 GiB window). VK_EXT_memory_budget would refine
|
||||
// this to the window's *remaining* space; see the header note.
|
||||
constexpr VkDeviceSize barWindowFraction = 8;
|
||||
return size <= barHeap / barWindowFraction;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue