BuildTLAS reallocated the host-visible instanceBuffer and metadataBuffer on every topology change. They only ever need to hold at least primitiveCount entries (the AS build reads exactly primitiveCount via tlasRangeInfo, and the copy loop writes [0, primitiveCount)), so a shrink — or any growth that still fits the previous capacity — can reuse the existing allocation. Gate the two Resize calls on a high-water-mark check, removing two of the four reallocations on a count change. The AS storage + scratch rebuild below is unchanged: it is tied to the AS itself and dominates this path regardless. Adds tests/TLASHighWaterMark driving the real hardware AS-build path: it asserts a shrink (and within-capacity growth) reuses the buffers while a growth past the high-water reallocates, that builtInstanceCount still tracks the live count, and that feeding an oversized instance buffer to the build produces zero Vulkan validation errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
624 lines
33 KiB
C++
624 lines
33 KiB
C++
import std;
|
|
import Crafter.Build;
|
|
namespace fs = std::filesystem;
|
|
using namespace Crafter;
|
|
|
|
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
|
|
std::vector<std::string> depArgs(args.begin(), args.end());
|
|
|
|
Configuration* event = GitProject({
|
|
.source = { .url = "https://forgejo.catcrafts.net/Catcrafts/Crafter.Event.git" },
|
|
.args = depArgs,
|
|
});
|
|
Configuration* math = GitProject({
|
|
.source = { "https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git" },
|
|
.args = depArgs,
|
|
});
|
|
Configuration* asset = GitProject({
|
|
.source = { "https://forgejo.catcrafts.net/Catcrafts/Crafter.Asset.git" },
|
|
.args = depArgs,
|
|
});
|
|
|
|
Configuration cfg;
|
|
cfg.path = "./";
|
|
cfg.name = "Crafter.Graphics";
|
|
cfg.outputName = "Crafter.Graphics";
|
|
cfg.type = ConfigurationType::LibraryStatic;
|
|
auto opts = ApplyStandardArgs(cfg, args);
|
|
cfg.dependencies = { event, math, asset };
|
|
|
|
// Window backend follows the target triple. V1 had separate lib-wayland /
|
|
// lib-win32 configurations; V2 picks the right one automatically based on
|
|
// where the build is going. Cross-compile (`--target=...`) flips the
|
|
// backend along with everything else. The DOM backend is reached by any
|
|
// wasm32-* target and produces a Vulkan-free build whose Window is wired
|
|
// to a custom JS env (see additional/dom-env.js).
|
|
bool dom = cfg.target.find("wasm") != std::string::npos;
|
|
bool windows = !dom
|
|
&& (cfg.target.find("windows") != std::string::npos
|
|
|| cfg.target.find("mingw") != std::string::npos);
|
|
if (dom) {
|
|
cfg.defines.push_back({"CRAFTER_GRAPHICS_WINDOW_DOM", ""});
|
|
// No native window libs, no Vulkan loader, no Wayland/X11. The JS
|
|
// bridge satisfies every dynamic symbol via wasm imports. Crafter.Build
|
|
// strips -march/-mtune from the clang command line for any wasm32-*
|
|
// triple, so cfg.march/mtune can stay at their defaults — keeping them
|
|
// matches the VariantId of dependency PCMs.
|
|
//
|
|
// WasmAlloc / WasmFree live in Crafter.Graphics-Dom.cpp and back
|
|
// dom-env.js's __writeUtf8 path (every keyboard / text-input event
|
|
// routes through them). The TU defines no symbols main.cpp would
|
|
// reference, so wasm-ld dead-strips it from libCrafter.Graphics.a
|
|
// for examples that don't touch the `Dom::HtmlElement*` API (like
|
|
// Sponza). `--export=` both forces the export AND pulls the
|
|
// defining .o in — solving both halves of the dead-strip problem.
|
|
cfg.linkFlags.push_back("-Wl,--export=WasmAlloc");
|
|
cfg.linkFlags.push_back("-Wl,--export=WasmFree");
|
|
} else if (windows) {
|
|
cfg.defines.push_back({"CRAFTER_GRAPHICS_WINDOW_WIN32", ""});
|
|
cfg.linkFlags.push_back("-lkernel32");
|
|
cfg.linkFlags.push_back("-luser32");
|
|
cfg.linkFlags.push_back("-lgdi32");
|
|
// Windows.Gaming.Input (WGI) needs the WinRT activation runtime
|
|
// and combase for HSTRING / RoGetActivationFactory.
|
|
cfg.linkFlags.push_back("-lruntimeobject");
|
|
cfg.linkFlags.push_back("-lcombase");
|
|
} else {
|
|
cfg.defines.push_back({"CRAFTER_GRAPHICS_WINDOW_WAYLAND", ""});
|
|
cfg.linkFlags.push_back("-lwayland-client");
|
|
cfg.linkFlags.push_back("-lxkbcommon");
|
|
// Gamepad: libudev for hot-plug + device enumeration; libevdev
|
|
// for event parsing + axis calibration. libevdev ships its headers
|
|
// under a versioned dir (libevdev-1.0/) so the -I is mandatory.
|
|
cfg.linkFlags.push_back("-ludev");
|
|
cfg.linkFlags.push_back("-levdev");
|
|
cfg.compileFlags.push_back("-I/usr/include/libevdev-1.0");
|
|
cfg.cFiles.push_back("lib/xdg-shell-protocol");
|
|
cfg.cFiles.push_back("lib/wayland-xdg-decoration-unstable-v1-client-protocol");
|
|
cfg.cFiles.push_back("lib/fractional-scale-v1");
|
|
cfg.cFiles.push_back("lib/viewporter");
|
|
}
|
|
|
|
// Vulkan is the only renderer on native targets. Software fallback is
|
|
// provided externally via the Vulkan loader (e.g. llvmpipe / lavapipe) —
|
|
// no separate code path. The DOM backend doesn't render in V1 (the
|
|
// UIRenderer and every Vulkan-typed module are excluded below); the
|
|
// WebGPU follow-up will gain its own headers/loader rather than reuse
|
|
// the Vulkan ones.
|
|
if (!dom) {
|
|
ExternalDependency& vkHeaders = cfg.externalDependencies.emplace_back();
|
|
vkHeaders.name = "Vulkan-Headers";
|
|
vkHeaders.source.url = "https://github.com/KhronosGroup/Vulkan-Headers.git";
|
|
vkHeaders.builder = ExternalBuilder::None;
|
|
vkHeaders.includeDirs = { "include" };
|
|
ExternalDependency& vkUtility = cfg.externalDependencies.emplace_back();
|
|
vkUtility.name = "Vulkan-Utility-Libraries";
|
|
vkUtility.source.url = "https://github.com/KhronosGroup/Vulkan-Utility-Libraries.git";
|
|
vkUtility.builder = ExternalBuilder::None;
|
|
vkUtility.includeDirs = { "include" };
|
|
cfg.linkFlags.push_back(windows ? "-lvulkan-1" : "-lvulkan");
|
|
}
|
|
|
|
if (opts.Has("--timing")) cfg.defines.push_back({"CRAFTER_TIMING", ""});
|
|
|
|
// One master interface list. Every partition exists on every target
|
|
// — Crafter.Build's dependency scanner doesn't respect `#ifdef` on
|
|
// `import :X` statements, so the partition file must be present even
|
|
// when its body is gated out. Vulkan-typed partitions stub to empty
|
|
// modules under CRAFTER_GRAPHICS_WINDOW_DOM; the Dom/DomEvents/Router
|
|
// partitions stub to empty modules in the opposite direction.
|
|
std::array<fs::path, 42> ifaces = {
|
|
"interfaces/Crafter.Graphics",
|
|
"interfaces/Crafter.Graphics-Animation",
|
|
"interfaces/Crafter.Graphics-Clipboard",
|
|
"interfaces/Crafter.Graphics-ComputeShader",
|
|
"interfaces/Crafter.Graphics-Decompress",
|
|
"interfaces/Crafter.Graphics-DescriptorHeapVulkan",
|
|
"interfaces/Crafter.Graphics-DescriptorHeapWebGPU",
|
|
"interfaces/Crafter.Graphics-Device",
|
|
"interfaces/Crafter.Graphics-Dom",
|
|
"interfaces/Crafter.Graphics-DomEvents",
|
|
"interfaces/Crafter.Graphics-Font",
|
|
"interfaces/Crafter.Graphics-FontAtlas",
|
|
"interfaces/Crafter.Graphics-ForwardDeclarations",
|
|
"interfaces/Crafter.Graphics-Gamepad",
|
|
"interfaces/Crafter.Graphics-GraphicsTypes",
|
|
"interfaces/Crafter.Graphics-Image2D",
|
|
"interfaces/Crafter.Graphics-ImageVulkan",
|
|
"interfaces/Crafter.Graphics-Input",
|
|
"interfaces/Crafter.Graphics-InputField",
|
|
"interfaces/Crafter.Graphics-Keys",
|
|
"interfaces/Crafter.Graphics-Mesh",
|
|
"interfaces/Crafter.Graphics-PipelineRTVulkan",
|
|
"interfaces/Crafter.Graphics-PipelineRTWebGPU",
|
|
"interfaces/Crafter.Graphics-PlainComputeShader",
|
|
"interfaces/Crafter.Graphics-RenderingElement3D",
|
|
"interfaces/Crafter.Graphics-RenderPass",
|
|
"interfaces/Crafter.Graphics-Router",
|
|
"interfaces/Crafter.Graphics-RT",
|
|
"interfaces/Crafter.Graphics-RTPass",
|
|
"interfaces/Crafter.Graphics-SamplerVulkan",
|
|
"interfaces/Crafter.Graphics-ShaderBindingTableVulkan",
|
|
"interfaces/Crafter.Graphics-ShaderBindingTableWebGPU",
|
|
"interfaces/Crafter.Graphics-ShaderVulkan",
|
|
"interfaces/Crafter.Graphics-Types",
|
|
"interfaces/Crafter.Graphics-UI",
|
|
"interfaces/Crafter.Graphics-UIComponents",
|
|
"interfaces/Crafter.Graphics-VulkanBuffer",
|
|
"interfaces/Crafter.Graphics-VulkanTransition",
|
|
"interfaces/Crafter.Graphics-WebGPU",
|
|
"interfaces/Crafter.Graphics-WebGPUBuffer",
|
|
"interfaces/Crafter.Graphics-WebGPUComputeShader",
|
|
"interfaces/Crafter.Graphics-Window",
|
|
};
|
|
|
|
if (dom) {
|
|
// DOM impl set. UI-Shared.cpp is backend-agnostic; UI-WebGPU.cpp
|
|
// is the DOM-only implementation of UIRenderer's GPU-touching
|
|
// methods. Font / FontAtlas / UIComponents / InputField are now
|
|
// portable.
|
|
std::array<fs::path, 17> domImpls = {
|
|
"implementations/Crafter.Graphics-Clipboard",
|
|
"implementations/Crafter.Graphics-Dom",
|
|
"implementations/Crafter.Graphics-Font",
|
|
"implementations/Crafter.Graphics-FontAtlas",
|
|
"implementations/Crafter.Graphics-Gamepad",
|
|
"implementations/Crafter.Graphics-Input",
|
|
"implementations/Crafter.Graphics-InputField",
|
|
"implementations/Crafter.Graphics-Mesh-WebGPU",
|
|
"implementations/Crafter.Graphics-PipelineRTWebGPU",
|
|
"implementations/Crafter.Graphics-RenderingElement3D-WebGPU",
|
|
"implementations/Crafter.Graphics-Router",
|
|
"implementations/Crafter.Graphics-ShaderBindingTableWebGPU",
|
|
"implementations/Crafter.Graphics-UI-Shared",
|
|
"implementations/Crafter.Graphics-UI-WebGPU",
|
|
"implementations/Crafter.Graphics-UIComponents",
|
|
"implementations/Crafter.Graphics-WebGPUComputeShader",
|
|
"implementations/Crafter.Graphics-Window",
|
|
};
|
|
cfg.GetInterfacesAndImplementations(ifaces, domImpls);
|
|
// JS glue shipped alongside the .wasm so the loader has the
|
|
// env-import surface the Window/Dom bindings expect.
|
|
cfg.files.emplace_back(fs::path("additional/dom-env.js"));
|
|
cfg.files.emplace_back(fs::path("additional/dom-webgpu.js"));
|
|
} else {
|
|
std::array<fs::path, 14> impls = {
|
|
"implementations/Crafter.Graphics-Clipboard",
|
|
"implementations/Crafter.Graphics-ComputeShader",
|
|
"implementations/Crafter.Graphics-Device",
|
|
"implementations/Crafter.Graphics-Font",
|
|
"implementations/Crafter.Graphics-FontAtlas",
|
|
"implementations/Crafter.Graphics-Gamepad",
|
|
"implementations/Crafter.Graphics-Input",
|
|
"implementations/Crafter.Graphics-InputField",
|
|
"implementations/Crafter.Graphics-Mesh",
|
|
"implementations/Crafter.Graphics-RenderingElement3D",
|
|
"implementations/Crafter.Graphics-UI",
|
|
"implementations/Crafter.Graphics-UI-Shared",
|
|
"implementations/Crafter.Graphics-UIComponents",
|
|
"implementations/Crafter.Graphics-Window",
|
|
};
|
|
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
|
|
|
cfg.shaders.emplace_back(fs::path("shaders/ui-quads.comp.glsl"), std::string("main"), ShaderType::Compute);
|
|
cfg.shaders.emplace_back(fs::path("shaders/ui-circles.comp.glsl"), std::string("main"), ShaderType::Compute);
|
|
cfg.shaders.emplace_back(fs::path("shaders/ui-images.comp.glsl"), std::string("main"), ShaderType::Compute);
|
|
cfg.shaders.emplace_back(fs::path("shaders/ui-text.comp.glsl"), std::string("main"), ShaderType::Compute);
|
|
cfg.shaders.emplace_back(fs::path("shaders/ui-fused.comp.glsl"), std::string("main"), ShaderType::Compute);
|
|
cfg.buildFiles.emplace_back(fs::path("shaders/ui-shared.glsl"));
|
|
|
|
// Regression test for issue #18: drive the NVIDIA descriptor-heap
|
|
// AS-read workaround's SPIR-V rewrite over real compiled shaders and
|
|
// check the result with spirv-val (one push-constant block, correct
|
|
// TLAS offset). The test executable recompiles the whole module plus
|
|
// tests/PushConstantRewrite/main.cpp; Configuration isn't copyable
|
|
// (it owns the parsed module list), so the shared build settings are
|
|
// mirrored field by field. glslang and spirv-val are invoked at
|
|
// runtime, so the test declares them as required tools. Remove with
|
|
// the rest of the workaround.
|
|
Test pcTest;
|
|
Configuration& tc = pcTest.config;
|
|
tc.path = cfg.path;
|
|
tc.name = "PushConstantRewrite";
|
|
tc.outputName = "PushConstantRewrite";
|
|
tc.type = ConfigurationType::Executable;
|
|
tc.target = cfg.target;
|
|
tc.march = cfg.march;
|
|
tc.mtune = cfg.mtune;
|
|
tc.debug = cfg.debug;
|
|
tc.sysroot = cfg.sysroot;
|
|
tc.dependencies = cfg.dependencies;
|
|
tc.externalDependencies = cfg.externalDependencies;
|
|
tc.compileFlags = cfg.compileFlags;
|
|
tc.linkFlags = cfg.linkFlags;
|
|
tc.defines = cfg.defines;
|
|
tc.cFiles = cfg.cFiles;
|
|
std::vector<fs::path> testImpls(impls.begin(), impls.end());
|
|
testImpls.emplace_back("tests/PushConstantRewrite/main");
|
|
tc.GetInterfacesAndImplementations(ifaces, testImpls);
|
|
pcTest.requires_ = { "tool:glslang", "tool:spirv-val" };
|
|
cfg.tests.push_back(std::move(pcTest));
|
|
|
|
// Regression test for issue #32: the Wayland scroll-wheel chain
|
|
// (wl_pointer.axis → Window::onMouseScroll). Drives the listener
|
|
// callback directly, so it needs no compositor — but it does need
|
|
// the Wayland backend compiled in, hence Linux-only.
|
|
if (!windows) {
|
|
Test scrollTest;
|
|
Configuration& sc = scrollTest.config;
|
|
sc.path = cfg.path;
|
|
sc.name = "MouseScroll";
|
|
sc.outputName = "MouseScroll";
|
|
sc.type = ConfigurationType::Executable;
|
|
sc.target = cfg.target;
|
|
sc.march = cfg.march;
|
|
sc.mtune = cfg.mtune;
|
|
sc.debug = cfg.debug;
|
|
sc.sysroot = cfg.sysroot;
|
|
sc.dependencies = cfg.dependencies;
|
|
sc.externalDependencies = cfg.externalDependencies;
|
|
sc.compileFlags = cfg.compileFlags;
|
|
sc.linkFlags = cfg.linkFlags;
|
|
sc.defines = cfg.defines;
|
|
sc.cFiles = cfg.cFiles;
|
|
std::vector<fs::path> scrollImpls(impls.begin(), impls.end());
|
|
scrollImpls.emplace_back("tests/MouseScroll/main");
|
|
sc.GetInterfacesAndImplementations(ifaces, scrollImpls);
|
|
cfg.tests.push_back(std::move(scrollTest));
|
|
|
|
// Issue #40: multi-frame-in-flight frame pacing (per-image fences
|
|
// + per-frame semaphores, no steady-state wait-idle). Drives the
|
|
// real frame loop against a live Wayland compositor for many more
|
|
// frames than there are in-flight slots and asserts the validation
|
|
// layer stays silent — the old singleton-semaphore design only
|
|
// avoided being an active race because the wait-idle masked it, so
|
|
// a clean multi-frame run is the regression guard. Needs the
|
|
// Wayland backend + a real compositor, hence inside the !windows
|
|
// block alongside MouseScroll.
|
|
Test frameLoopTest;
|
|
Configuration& fl = frameLoopTest.config;
|
|
fl.path = cfg.path;
|
|
fl.name = "FrameLoopSync";
|
|
fl.outputName = "FrameLoopSync";
|
|
fl.type = ConfigurationType::Executable;
|
|
fl.target = cfg.target;
|
|
fl.march = cfg.march;
|
|
fl.mtune = cfg.mtune;
|
|
fl.debug = cfg.debug;
|
|
fl.sysroot = cfg.sysroot;
|
|
fl.dependencies = cfg.dependencies;
|
|
fl.externalDependencies = cfg.externalDependencies;
|
|
fl.compileFlags = cfg.compileFlags;
|
|
fl.linkFlags = cfg.linkFlags;
|
|
fl.defines = cfg.defines;
|
|
fl.cFiles = cfg.cFiles;
|
|
std::vector<fs::path> frameLoopImpls(impls.begin(), impls.end());
|
|
frameLoopImpls.emplace_back("tests/FrameLoopSync/main");
|
|
fl.GetInterfacesAndImplementations(ifaces, frameLoopImpls);
|
|
cfg.tests.push_back(std::move(frameLoopTest));
|
|
}
|
|
|
|
// Issue #36: BLAS build options. Drives the real hardware AS-build
|
|
// path — records Mesh::Build / Refit / BuildProcedural /
|
|
// RefitProcedural with fast-build/fast-trace + allow-update flags
|
|
// into one-time command buffers and submits them, asserting the
|
|
// requested flags land, that an allowUpdate refit keeps the AS
|
|
// handle (in-place UPDATE), and that the validation layer reports no
|
|
// errors. Needs a Vulkan RT device at runtime (same as the RT
|
|
// examples), so it shares the native build settings.
|
|
Test blasTest;
|
|
Configuration& bc = blasTest.config;
|
|
bc.path = cfg.path;
|
|
bc.name = "BLASBuildOptions";
|
|
bc.outputName = "BLASBuildOptions";
|
|
bc.type = ConfigurationType::Executable;
|
|
bc.target = cfg.target;
|
|
bc.march = cfg.march;
|
|
bc.mtune = cfg.mtune;
|
|
bc.debug = cfg.debug;
|
|
bc.sysroot = cfg.sysroot;
|
|
bc.dependencies = cfg.dependencies;
|
|
bc.externalDependencies = cfg.externalDependencies;
|
|
bc.compileFlags = cfg.compileFlags;
|
|
bc.linkFlags = cfg.linkFlags;
|
|
bc.defines = cfg.defines;
|
|
bc.cFiles = cfg.cFiles;
|
|
std::vector<fs::path> blasImpls(impls.begin(), impls.end());
|
|
blasImpls.emplace_back("tests/BLASBuildOptions/main");
|
|
bc.GetInterfacesAndImplementations(ifaces, blasImpls);
|
|
cfg.tests.push_back(std::move(blasTest));
|
|
|
|
// Issue #64: TLAS host-input buffers (instanceBuffer / metadataBuffer)
|
|
// grow on a high-water mark instead of reallocating to the exact count
|
|
// every topology change. Drives the real hardware AS-build path — a
|
|
// cube BLAS plus RenderingElement3D::BuildTLAS at a sequence of
|
|
// instance counts — and asserts that a shrink (and any growth within
|
|
// the high-water capacity) reuses the existing allocation while a
|
|
// growth past it reallocates, with the validation layer reporting no
|
|
// errors when an oversized instance buffer is fed to the build. Needs a
|
|
// Vulkan RT device at runtime, so it shares the native build settings.
|
|
Test tlasTest;
|
|
Configuration& tlc = tlasTest.config;
|
|
tlc.path = cfg.path;
|
|
tlc.name = "TLASHighWaterMark";
|
|
tlc.outputName = "TLASHighWaterMark";
|
|
tlc.type = ConfigurationType::Executable;
|
|
tlc.target = cfg.target;
|
|
tlc.march = cfg.march;
|
|
tlc.mtune = cfg.mtune;
|
|
tlc.debug = cfg.debug;
|
|
tlc.sysroot = cfg.sysroot;
|
|
tlc.dependencies = cfg.dependencies;
|
|
tlc.externalDependencies = cfg.externalDependencies;
|
|
tlc.compileFlags = cfg.compileFlags;
|
|
tlc.linkFlags = cfg.linkFlags;
|
|
tlc.defines = cfg.defines;
|
|
tlc.cFiles = cfg.cFiles;
|
|
std::vector<fs::path> tlasImpls(impls.begin(), impls.end());
|
|
tlasImpls.emplace_back("tests/TLASHighWaterMark/main");
|
|
tlc.GetInterfacesAndImplementations(ifaces, tlasImpls);
|
|
cfg.tests.push_back(std::move(tlasTest));
|
|
|
|
// Issue #51: FontAtlas only re-uploads the dirty sub-rect now,
|
|
// tracked via FontAtlas::DirtyRect. The accumulation/clamp math is
|
|
// pure CPU, so this test drives it directly — no GPU device needed
|
|
// at runtime (the real copy params are covered by HelloUI rendering
|
|
// text on both backends).
|
|
Test atlasTest;
|
|
Configuration& ac = atlasTest.config;
|
|
ac.path = cfg.path;
|
|
ac.name = "FontAtlasDirtyRect";
|
|
ac.outputName = "FontAtlasDirtyRect";
|
|
ac.type = ConfigurationType::Executable;
|
|
ac.target = cfg.target;
|
|
ac.march = cfg.march;
|
|
ac.mtune = cfg.mtune;
|
|
ac.debug = cfg.debug;
|
|
ac.sysroot = cfg.sysroot;
|
|
ac.dependencies = cfg.dependencies;
|
|
ac.externalDependencies = cfg.externalDependencies;
|
|
ac.compileFlags = cfg.compileFlags;
|
|
ac.linkFlags = cfg.linkFlags;
|
|
ac.defines = cfg.defines;
|
|
ac.cFiles = cfg.cFiles;
|
|
std::vector<fs::path> atlasImpls(impls.begin(), impls.end());
|
|
atlasImpls.emplace_back("tests/FontAtlasDirtyRect/main");
|
|
ac.GetInterfacesAndImplementations(ifaces, atlasImpls);
|
|
cfg.tests.push_back(std::move(atlasTest));
|
|
|
|
// Issue #52: shaped-run cache for UIRenderer::ShapeText. Shapes a
|
|
// string against a real CPU-side FontAtlas and asserts that a cache
|
|
// hit is byte-equivalent to the uncached path, that hits don't touch
|
|
// the atlas, and that translate / alignment / truncation /
|
|
// InvalidateFont all behave. Needs a headless Vulkan device (the
|
|
// atlas image is a real GPU image) but no swapchain — same native
|
|
// build settings as the others. The font file is copied next to the
|
|
// binary; the test also probes the project-root path.
|
|
Test textTest;
|
|
Configuration& xc = textTest.config;
|
|
xc.path = cfg.path;
|
|
xc.name = "ShapeTextCache";
|
|
xc.outputName = "ShapeTextCache";
|
|
xc.type = ConfigurationType::Executable;
|
|
xc.target = cfg.target;
|
|
xc.march = cfg.march;
|
|
xc.mtune = cfg.mtune;
|
|
xc.debug = cfg.debug;
|
|
xc.sysroot = cfg.sysroot;
|
|
xc.dependencies = cfg.dependencies;
|
|
xc.externalDependencies = cfg.externalDependencies;
|
|
xc.compileFlags = cfg.compileFlags;
|
|
xc.linkFlags = cfg.linkFlags;
|
|
xc.defines = cfg.defines;
|
|
xc.cFiles = cfg.cFiles;
|
|
xc.files = { fs::path("tests/ShapeTextCache/font.ttf") };
|
|
std::vector<fs::path> textImpls(impls.begin(), impls.end());
|
|
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));
|
|
|
|
// Issue #47: the fused UI uber-kernel (shaders/ui-fused.comp.glsl) and
|
|
// its C++ push-constant mirror UIFusedHeader. Compiles the real shader
|
|
// with glslang, validates with spirv-val, and pins the push-constant
|
|
// member offsets to UIFusedHeader's layout so a GLSL/C++ drift can't
|
|
// slip through (the C++ static_assert only guards the C++ side). No GPU
|
|
// device at runtime, but glslang + spirv-val are required tools.
|
|
Test fusedTest;
|
|
Configuration& ftc = fusedTest.config;
|
|
ftc.path = cfg.path;
|
|
ftc.name = "UIFusedShader";
|
|
ftc.outputName = "UIFusedShader";
|
|
ftc.type = ConfigurationType::Executable;
|
|
ftc.target = cfg.target;
|
|
ftc.march = cfg.march;
|
|
ftc.mtune = cfg.mtune;
|
|
ftc.debug = cfg.debug;
|
|
ftc.sysroot = cfg.sysroot;
|
|
ftc.dependencies = cfg.dependencies;
|
|
ftc.externalDependencies = cfg.externalDependencies;
|
|
ftc.compileFlags = cfg.compileFlags;
|
|
ftc.linkFlags = cfg.linkFlags;
|
|
ftc.defines = cfg.defines;
|
|
ftc.cFiles = cfg.cFiles;
|
|
std::vector<fs::path> fusedImpls(impls.begin(), impls.end());
|
|
fusedImpls.emplace_back("tests/UIFusedShader/main");
|
|
ftc.GetInterfacesAndImplementations(ifaces, fusedImpls);
|
|
fusedTest.requires_ = { "tool:glslang", "tool:spirv-val" };
|
|
cfg.tests.push_back(std::move(fusedTest));
|
|
|
|
// Issue #60: VulkanBuffer::FlushDevice / FlushHost now record the chosen
|
|
// memory type's propertyFlags at Create time and skip the
|
|
// flush/invalidate when the memory is HOST_COHERENT. The gate is pure
|
|
// logic over the recorded flags, so this test stamps them directly and
|
|
// verifies the coherent path issues no Vulkan call — no GPU device
|
|
// needed at runtime.
|
|
Test flushTest;
|
|
Configuration& fc = flushTest.config;
|
|
fc.path = cfg.path;
|
|
fc.name = "VulkanBufferFlushGate";
|
|
fc.outputName = "VulkanBufferFlushGate";
|
|
fc.type = ConfigurationType::Executable;
|
|
fc.target = cfg.target;
|
|
fc.march = cfg.march;
|
|
fc.mtune = cfg.mtune;
|
|
fc.debug = cfg.debug;
|
|
fc.sysroot = cfg.sysroot;
|
|
fc.dependencies = cfg.dependencies;
|
|
fc.externalDependencies = cfg.externalDependencies;
|
|
fc.compileFlags = cfg.compileFlags;
|
|
fc.linkFlags = cfg.linkFlags;
|
|
fc.defines = cfg.defines;
|
|
fc.cFiles = cfg.cFiles;
|
|
std::vector<fs::path> flushImpls(impls.begin(), impls.end());
|
|
flushImpls.emplace_back("tests/VulkanBufferFlushGate/main");
|
|
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
|
|
// query. Pure CPU — Font only touches stb_truetype — so this drives
|
|
// the public API directly with no Vulkan device. The font file is
|
|
// copied next to the binary; the test also probes the project root.
|
|
Test advTest;
|
|
Configuration& vc = advTest.config;
|
|
vc.path = cfg.path;
|
|
vc.name = "FontAdvanceCache";
|
|
vc.outputName = "FontAdvanceCache";
|
|
vc.type = ConfigurationType::Executable;
|
|
vc.target = cfg.target;
|
|
vc.march = cfg.march;
|
|
vc.mtune = cfg.mtune;
|
|
vc.debug = cfg.debug;
|
|
vc.sysroot = cfg.sysroot;
|
|
vc.dependencies = cfg.dependencies;
|
|
vc.externalDependencies = cfg.externalDependencies;
|
|
vc.compileFlags = cfg.compileFlags;
|
|
vc.linkFlags = cfg.linkFlags;
|
|
vc.defines = cfg.defines;
|
|
vc.cFiles = cfg.cFiles;
|
|
vc.files = { fs::path("tests/FontAdvanceCache/font.ttf") };
|
|
std::vector<fs::path> advImpls(impls.begin(), impls.end());
|
|
advImpls.emplace_back("tests/FontAdvanceCache/main");
|
|
vc.GetInterfacesAndImplementations(ifaces, advImpls);
|
|
cfg.tests.push_back(std::move(advTest));
|
|
|
|
// Issue #50: uiResolveScreenPixel now gates its per-pixel clip-rect
|
|
// compares on the reserved kUIFlagClip bit, which FillHeader sets only
|
|
// when the clip rect is narrower than the surface. The decision lives
|
|
// in UIRenderer::ClipFlags — pure CPU logic over the clip rect and the
|
|
// surface size — so this test drives it directly with synthetic
|
|
// dimensions, no Window or GPU device needed at runtime.
|
|
Test clipTest;
|
|
Configuration& clc = clipTest.config;
|
|
clc.path = cfg.path;
|
|
clc.name = "UIClipFlag";
|
|
clc.outputName = "UIClipFlag";
|
|
clc.type = ConfigurationType::Executable;
|
|
clc.target = cfg.target;
|
|
clc.march = cfg.march;
|
|
clc.mtune = cfg.mtune;
|
|
clc.debug = cfg.debug;
|
|
clc.sysroot = cfg.sysroot;
|
|
clc.dependencies = cfg.dependencies;
|
|
clc.externalDependencies = cfg.externalDependencies;
|
|
clc.compileFlags = cfg.compileFlags;
|
|
clc.linkFlags = cfg.linkFlags;
|
|
clc.defines = cfg.defines;
|
|
clc.cFiles = cfg.cFiles;
|
|
std::vector<fs::path> clipImpls(impls.begin(), impls.end());
|
|
clipImpls.emplace_back("tests/UIClipFlag/main");
|
|
clc.GetInterfacesAndImplementations(ifaces, clipImpls);
|
|
cfg.tests.push_back(std::move(clipTest));
|
|
|
|
// Issue #56: InputField_HitTestCursor mapped a click x to a cursor byte
|
|
// offset by re-walking the prefix for every boundary (O(n^2) glyph
|
|
// metric lookups) over raw byte boundaries. It now delegates to
|
|
// Font::NearestCursorByte — one cumulative-advance pass over codepoint
|
|
// boundaries. The mapping is pure CPU over a TrueType file, so this test
|
|
// drives it directly: no Vulkan device or window. The font is copied
|
|
// next to the binary; the test also probes the project-root path.
|
|
Test hitTest;
|
|
Configuration& hc = hitTest.config;
|
|
hc.path = cfg.path;
|
|
hc.name = "InputFieldHitTest";
|
|
hc.outputName = "InputFieldHitTest";
|
|
hc.type = ConfigurationType::Executable;
|
|
hc.target = cfg.target;
|
|
hc.march = cfg.march;
|
|
hc.mtune = cfg.mtune;
|
|
hc.debug = cfg.debug;
|
|
hc.sysroot = cfg.sysroot;
|
|
hc.dependencies = cfg.dependencies;
|
|
hc.externalDependencies = cfg.externalDependencies;
|
|
hc.compileFlags = cfg.compileFlags;
|
|
hc.linkFlags = cfg.linkFlags;
|
|
hc.defines = cfg.defines;
|
|
hc.cFiles = cfg.cFiles;
|
|
hc.files = { fs::path("tests/InputFieldHitTest/font.ttf") };
|
|
std::vector<fs::path> hitImpls(impls.begin(), impls.end());
|
|
hitImpls.emplace_back("tests/InputFieldHitTest/main");
|
|
hc.GetInterfacesAndImplementations(ifaces, hitImpls);
|
|
cfg.tests.push_back(std::move(hitTest));
|
|
}
|
|
|
|
return cfg;
|
|
}
|