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:
catbot 2026-06-16 17:23:27 +00:00
commit cfbe181d9e
4 changed files with 275 additions and 0 deletions

View file

@ -206,6 +206,34 @@ export namespace Crafter {
// valid memory exists for the allocation).
static std::uint32_t GetMemoryType(std::uint32_t typeBits, VkMemoryPropertyFlags required, VkMemoryPropertyFlags preferred = 0);
// Upload-strategy helper for a CPU-written, GPU-read buffer of `size`
// bytes. Picks *where* the buffer should live, complementing
// GetMemoryType (which picks the memory type once the strategy is set):
// true -> allocate HOST_VISIBLE | DEVICE_LOCAL, map, write straight
// into device memory (no staging buffer, no copy).
// false -> allocate pure DEVICE_LOCAL and feed it from a HOST_VISIBLE
// staging buffer + vkCmdCopyBuffer.
//
// The decision is platform-dependent and made at runtime, never
// hardcoded:
// 1. No DEVICE_LOCAL | HOST_VISIBLE type exists (no resizable BAR) ->
// false: direct writes are impossible, staging is mandatory.
// 2. ReBAR / UMA — the host-visible device-local heap is essentially
// the whole VRAM heap (>= 90% of the largest DEVICE_LOCAL heap) ->
// true: a staging copy would be pure overhead.
// 3. Small BAR window — the host-visible device-local heap is a tiny
// window on a separate heap (<< VRAM) -> true only for buffers
// small enough to fit the window's per-buffer budget; large
// buffers stage so they don't exhaust the window (issue #58).
//
// This is the *upload* path (CPU-write -> GPU-read). Readback wants
// HOST_CACHED instead (the ReBAR type is coherent-not-cached, so CPU
// reads from it are slow) — keep that path separate. A more robust
// small-window budget would consult VK_EXT_memory_budget for the
// window's *remaining* space rather than its total size; that needs the
// extension enabled and is a follow-up.
static bool PreferDirectDeviceWrite(VkDeviceSize size);
// ─── Wayland key repeat ────────────────────────────────────────
// TickKeyRepeats fires onRawKeyDown / onRawKeyHold / onTextInput on
// the focused window for whichever key is currently repeating.