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

@ -472,6 +472,35 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
fc.GetInterfacesAndImplementations(ifaces, flushImpls);
cfg.tests.push_back(std::move(flushTest));
// Issue #89: Device::PreferDirectDeviceWrite chooses the upload strategy
// for a CPU-written, GPU-read buffer — direct HOST_VISIBLE|DEVICE_LOCAL
// map+write on ReBAR/UMA vs. staged-into-pure-DEVICE_LOCAL on a small
// BAR window (#58). The decision is pure CPU logic over
// Device::memoryProperties (types + heaps), so this test installs
// synthetic ReBAR / UMA / small-window / no-BAR layouts and drives it
// directly — no GPU device needed at runtime.
Test uploadTest;
Configuration& uc = uploadTest.config;
uc.path = cfg.path;
uc.name = "UploadStrategy";
uc.outputName = "UploadStrategy";
uc.type = ConfigurationType::Executable;
uc.target = cfg.target;
uc.march = cfg.march;
uc.mtune = cfg.mtune;
uc.debug = cfg.debug;
uc.sysroot = cfg.sysroot;
uc.dependencies = cfg.dependencies;
uc.externalDependencies = cfg.externalDependencies;
uc.compileFlags = cfg.compileFlags;
uc.linkFlags = cfg.linkFlags;
uc.defines = cfg.defines;
uc.cFiles = cfg.cFiles;
std::vector<fs::path> uploadImpls(impls.begin(), impls.end());
uploadImpls.emplace_back("tests/UploadStrategy/main");
uc.GetInterfacesAndImplementations(ifaces, uploadImpls);
cfg.tests.push_back(std::move(uploadTest));
// Issue #57: Font::GetLineWidth memoises per-codepoint advances in
// font units (Font::AdvanceUnits) and rescales per call, instead of
// calling stbtt_GetCodepointHMetrics for every glyph on every caret