fix(device): add preferred mask + required-only fallback to GetMemoryType (#59)

GetMemoryType was a bare first-superset match that threw whenever no
memory type satisfied the requested property flags. Callers combining a
mandatory flag with a perf-only one (HOST_VISIBLE | DEVICE_LOCAL for the
descriptor heaps) would then fail allocation on any device without a
host-visible device-local heap (no resizable BAR).

It now takes a `preferred` mask distinct from `required`: a type
satisfying both is chosen first, falling back to a required-only match
when the preference is unavailable, and throwing only when even
`required` cannot be met. VulkanBuffer::Create/Resize gain an optional
trailing `preferredPropertyFlags`, and the descriptor heaps now treat
DEVICE_LOCAL as a preference on top of mandatory HOST_VISIBLE.

This does not touch any flush/barrier behaviour — coherency is not
assumed anywhere here.

Adds tests/MemoryTypeFallback, a pure-CPU test that installs synthetic
memory layouts and exercises selection, preference, fallback, and the
unsatisfiable-required throw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
catbot 2026-06-16 16:03:50 +00:00
commit 294c1378b5
6 changed files with 218 additions and 13 deletions

View file

@ -384,6 +384,35 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
textImpls.emplace_back("tests/ShapeTextCache/main");
xc.GetInterfacesAndImplementations(ifaces, textImpls);
cfg.tests.push_back(std::move(textTest));
// Issue #59: Device::GetMemoryType gained a `preferred` mask and a
// required-only fallback so callers combining a mandatory flag with a
// perf-only one (HOST_VISIBLE | DEVICE_LOCAL descriptor heaps) no
// longer throw on devices without a host-visible device-local heap.
// The selection is pure CPU logic over Device::memoryProperties, so
// this test installs synthetic memory layouts and drives it directly —
// no GPU device needed at runtime.
Test memTest;
Configuration& mc = memTest.config;
mc.path = cfg.path;
mc.name = "MemoryTypeFallback";
mc.outputName = "MemoryTypeFallback";
mc.type = ConfigurationType::Executable;
mc.target = cfg.target;
mc.march = cfg.march;
mc.mtune = cfg.mtune;
mc.debug = cfg.debug;
mc.sysroot = cfg.sysroot;
mc.dependencies = cfg.dependencies;
mc.externalDependencies = cfg.externalDependencies;
mc.compileFlags = cfg.compileFlags;
mc.linkFlags = cfg.linkFlags;
mc.defines = cfg.defines;
mc.cFiles = cfg.cFiles;
std::vector<fs::path> memImpls(impls.begin(), impls.end());
memImpls.emplace_back("tests/MemoryTypeFallback/main");
mc.GetInterfacesAndImplementations(ifaces, memImpls);
cfg.tests.push_back(std::move(memTest));
}
return cfg;