The mip-generation upload path issued N+1 layout-transition barriers per chain. The final blit's destination level is never read again, so its dedicated TRANSFER_DST -> TRANSFER_SRC barrier was redundant: the level was transitioned to SRC only to keep the final SRC -> consumer transition uniform. Skip that last per-level barrier and fold the final level into the closing transition, which now batches two VkImageMemoryBarrier entries ([0, mipLevels-1) from TRANSFER_SRC, the last level from TRANSFER_DST) into a single vkCmdPipelineBarrier. One fewer barrier call per chain; the N-1 interleaved per-level barriers stay one-at-a-time since each is read by the next blit. Applied to both the host-upload and GPU-decompress Update paths. Adds the MipChainBarrierBatch regression test driving the pure barrier builder (no GPU device needed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
649 lines
34 KiB
C++
649 lines
34 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 #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 #70: ImageVulkan's mip-chain upload folds the never-read final
|
|
// blit destination into a single batched final-transition barrier
|
|
// instead of giving it a dedicated DST->SRC barrier. The barrier set is
|
|
// built by BuildMipChainFinalBarriers, pure CPU logic over mip count and
|
|
// layout, so this test drives it directly — no GPU device at runtime.
|
|
Test mipBarrierTest;
|
|
Configuration& mbc = mipBarrierTest.config;
|
|
mbc.path = cfg.path;
|
|
mbc.name = "MipChainBarrierBatch";
|
|
mbc.outputName = "MipChainBarrierBatch";
|
|
mbc.type = ConfigurationType::Executable;
|
|
mbc.target = cfg.target;
|
|
mbc.march = cfg.march;
|
|
mbc.mtune = cfg.mtune;
|
|
mbc.debug = cfg.debug;
|
|
mbc.sysroot = cfg.sysroot;
|
|
mbc.dependencies = cfg.dependencies;
|
|
mbc.externalDependencies = cfg.externalDependencies;
|
|
mbc.compileFlags = cfg.compileFlags;
|
|
mbc.linkFlags = cfg.linkFlags;
|
|
mbc.defines = cfg.defines;
|
|
mbc.cFiles = cfg.cFiles;
|
|
std::vector<fs::path> mipBarrierImpls(impls.begin(), impls.end());
|
|
mipBarrierImpls.emplace_back("tests/MipChainBarrierBatch/main");
|
|
mbc.GetInterfacesAndImplementations(ifaces, mipBarrierImpls);
|
|
cfg.tests.push_back(std::move(mipBarrierTest));
|
|
|
|
// 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 #63: VulkanBuffer::Resize now reuses the existing allocation in
|
|
// place when a new request still fits the created capacity and the
|
|
// immutable-at-create properties match (usage flags fixed at create;
|
|
// chosen memory type still satisfies the required flags), instead of
|
|
// always destroying + reallocating. The reuse guard is pure logic over
|
|
// recorded fields, so this test stamps a fake handle + capacity/flags and
|
|
// verifies the in-place path issues no Vulkan call — no GPU device needed.
|
|
Test resizeTest;
|
|
Configuration& rc = resizeTest.config;
|
|
rc.path = cfg.path;
|
|
rc.name = "VulkanBufferResizeReuse";
|
|
rc.outputName = "VulkanBufferResizeReuse";
|
|
rc.type = ConfigurationType::Executable;
|
|
rc.target = cfg.target;
|
|
rc.march = cfg.march;
|
|
rc.mtune = cfg.mtune;
|
|
rc.debug = cfg.debug;
|
|
rc.sysroot = cfg.sysroot;
|
|
rc.dependencies = cfg.dependencies;
|
|
rc.externalDependencies = cfg.externalDependencies;
|
|
rc.compileFlags = cfg.compileFlags;
|
|
rc.linkFlags = cfg.linkFlags;
|
|
rc.defines = cfg.defines;
|
|
rc.cFiles = cfg.cFiles;
|
|
std::vector<fs::path> resizeImpls(impls.begin(), impls.end());
|
|
resizeImpls.emplace_back("tests/VulkanBufferResizeReuse/main");
|
|
rc.GetInterfacesAndImplementations(ifaces, resizeImpls);
|
|
cfg.tests.push_back(std::move(resizeTest));
|
|
|
|
// 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;
|
|
}
|