From 8c2131f74afa0e63b9cc4bdd3073e416bd94d908 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Wed, 12 Aug 2026 07:17:51 +0800 Subject: [PATCH] feat: add compat.miniaudio, spirv-reflect, nanosvg, vulkan-memory-allocator, gtl and plf-hive These six were the libraries XRGUI (Sunrisepeak/xrgui#1) still reached through git submodules and -I flags rather than through a package manager. Packaging them is not an mcpp-specific accommodation: xmake.lua:44 already reads add_requires("nanosvg", "spirv-reflect", "gtl", "glfw", "miniaudio") so the project's own primary build gets four of them from xrepo packages, and the external/ submodules beside them are vestigial. Two are Khronos/AMD official (SPIRV-Reflect, VulkanMemoryAllocator); the rest are widely used in their own right. THREE SHAPES, ONE OF THEM NEW TO THIS INDEX A header-only + anchor TU gtl, plf-hive B single header + GENERATED impl nanosvg, vulkan-memory-allocator C one upstream TU is the library miniaudio, spirv-reflect Shape B is the new one. Both are stb-style -- the implementation hides behind a macro and upstream ships no .c to instantiate it. Leaving that to the consumer would make the package a header drop rather than something linkable, and would hand every consumer the same duplicate-symbol hazard, so the package generates the TU once and both descriptors state the rule that follows: do not define the macro again (it is a LINK error, so it surfaces late). VMA FORCED A POLICY CHOICE, NOT JUST A SHAPE VMA defaults to VMA_STATIC_VULKAN_FUNCTIONS 1, which references vkBindBufferMemory2, vkGetPhysicalDeviceProperties2 and six more BY NAME. Against a headers-only dependency that is eight undefined symbols -- observed at link time, not predicted. Adding compat.vulkan would have made it link and would have been wrong: it forces a Vulkan loader on every consumer of a memory allocator and fights anyone dispatching through volk. The generated TU selects VMA_DYNAMIC_VULKAN_FUNCTIONS instead, so VMA resolves everything through VmaVulkanFunctions and the package needs the Vulkan headers alone. TWO SMALLER JUDGEMENTS WORTH KEEPING miniaudio links -ldl -lpthread -lm and deliberately NOT -lasound/-lpulse: it dlopen()s its backends, so hard-linking them would break the package on a machine that has neither, for no gain. spirv-reflect exposes BOTH `*` and `*/include`, because spirv_reflect.h:35-37 picks between and the bundled "./include/spirv/unified1/spirv.h". Exposing both makes the two spellings resolve to the SAME header, so a consumer that defines SPIRV_REFLECT_USE_SYSTEM_SPIRV_H cannot silently get a different SPIR-V revision than this .c was written against. VERSIONING THE UNTAGGED TWO nanosvg and plf_hive cut no tags. Following compat.khrplatform (which mirrors the untagged EGL-Registry), each pins a commit archive under a DATE version from that commit's date. spirv-reflect has the opposite problem -- it tags in lockstep with the Vulkan SDK -- so its key drops the `vulkan-sdk-` prefix and lines up with compat.vulkan-headers of the same SDK. TESTS ASSERT BEHAVIOUR, NOT LINKAGE Every test can fail. hive checks that element ADDRESSES survive erasing their neighbours; nanosvg checks rasterized pixel coverage is 2300-2700 for a 50x50 rect and links both halves of the generated TU so a half-instantiated package fails there; miniaudio round-trips a sine through WAV and also checks the peak so silence cannot pass vacuously; spirv-reflect reflects a REAL glslc-compiled shader (embedded as words, no compiler needed on the runner) and adds a negative case so an always-succeed stub cannot pass; VMA drives the virtual allocator and asserts no two live allocations overlap. VERIFIED LOCALLY - six `mcpp test` runs, 1 passed / 0 failed each - lint scripts pass on the six; cross-package refs pass across all 96 - `mcpp xpkg parse` passes for ALL 96 descriptors under the PINNED CI version 2026.8.10.3, downloaded for the purpose rather than trusting the newer local build -- that check is what enforces "floor first, grammar after" - all six CN mirrors return 200 and are byte-identical to GLOBAL, which is what mirror-cn-reachable checks --- README.md | 5 +- README.zh-CN.md | 5 +- ...6-08-12-add-xrgui-header-libraries-plan.md | 141 ++++++++++++++++ mcpp.toml | 6 + pkgs/c/compat.gtl.lua | 76 +++++++++ pkgs/c/compat.miniaudio.lua | 85 ++++++++++ pkgs/c/compat.nanosvg.lua | 95 +++++++++++ pkgs/c/compat.plf-hive.lua | 79 +++++++++ pkgs/c/compat.spirv-reflect.lua | 83 ++++++++++ pkgs/c/compat.vulkan-memory-allocator.lua | 113 +++++++++++++ tests/examples/gtl/mcpp.toml | 8 + tests/examples/gtl/tests/containers.cpp | 54 ++++++ tests/examples/miniaudio/mcpp.toml | 9 + tests/examples/miniaudio/tests/roundtrip.cpp | 90 ++++++++++ tests/examples/nanosvg/mcpp.toml | 9 + tests/examples/nanosvg/tests/parse.cpp | 85 ++++++++++ tests/examples/plf-hive/mcpp.toml | 12 ++ tests/examples/plf-hive/tests/hive.cpp | 64 ++++++++ tests/examples/spirv-reflect/mcpp.toml | 9 + .../examples/spirv-reflect/tests/reflect.cpp | 155 ++++++++++++++++++ .../vulkan-memory-allocator/mcpp.toml | 10 ++ .../tests/virtual_block.cpp | 97 +++++++++++ 22 files changed, 1286 insertions(+), 4 deletions(-) create mode 100644 docs/superpowers/specs/2026-08-12-add-xrgui-header-libraries-plan.md create mode 100644 pkgs/c/compat.gtl.lua create mode 100644 pkgs/c/compat.miniaudio.lua create mode 100644 pkgs/c/compat.nanosvg.lua create mode 100644 pkgs/c/compat.plf-hive.lua create mode 100644 pkgs/c/compat.spirv-reflect.lua create mode 100644 pkgs/c/compat.vulkan-memory-allocator.lua create mode 100644 tests/examples/gtl/mcpp.toml create mode 100644 tests/examples/gtl/tests/containers.cpp create mode 100644 tests/examples/miniaudio/mcpp.toml create mode 100644 tests/examples/miniaudio/tests/roundtrip.cpp create mode 100644 tests/examples/nanosvg/mcpp.toml create mode 100644 tests/examples/nanosvg/tests/parse.cpp create mode 100644 tests/examples/plf-hive/mcpp.toml create mode 100644 tests/examples/plf-hive/tests/hive.cpp create mode 100644 tests/examples/spirv-reflect/mcpp.toml create mode 100644 tests/examples/spirv-reflect/tests/reflect.cpp create mode 100644 tests/examples/vulkan-memory-allocator/mcpp.toml create mode 100644 tests/examples/vulkan-memory-allocator/tests/virtual_block.cpp diff --git a/README.md b/README.md index 56c2f3ac..adb450e8 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,11 @@ Two kinds of packages live here: | C++-source compat, one depending on the other | [`compat.abseil`](pkgs/c/compat.abseil.lua) (151 TUs; a wildcard over `absl/**` trimmed by upstream's test/benchmark naming conventions) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua) (the libprotobuf runtime, 79 TUs transcribed from upstream's own `src/file_lists.cmake`; declares `compat.abseil` as a dependency because protobuf's public headers include `absl/…`, and its `gzip` feature defines `HAVE_ZLIB` and pulls `compat.zlib`, while `upb` adds protobuf's 64-TU C runtime out of the same tarball. It also exposes **`protoc`** as a `kind = "bin"` target, so a consumer writing `tools = ["protoc"]` gets the compiler built for its own machine out of the same package it links — making a generator/runtime version mismatch inexpressible) · [`compat.re2`](pkgs/c/compat.re2.lua) (22 TUs, upstream's own `RE2_SOURCES`) · [`compat.redis-plus-plus`](pkgs/c/compat.redis-plus-plus.lua) (redis++ 1.3.13 — the sync client, 17 TUs + `patterns/redlock.cpp`, depends on `compat.hiredis`; the one header CMake would generate, `hiredis_features.h`, is snapshotted via `generated_files`, and the async/TLS TUs are left out so the base build stays a two-package pair. An `async` feature adds the libuv-backed `AsyncRedis` interface (the 9 async TUs + `compat.libuv`; `event_loop.cpp` runs `uv_run` on a background thread, and `` arrives through compat.hiredis' wrapper headers). Two versions, one on each side of the source-structure watershed, share this ONE source list: 1.3.13 (modern 17-TU layout) and 1.3.3 (pre-`redis_uri.cpp`/`redlock` 15-TU layout) — the union works because 1.3.3's TUs are a strict subset, so exactly two globs match nothing there (a warning, not an error; same trick as compat.catch2)) | | C++-source compat, zero-dep client + optional components | [`compat.websocket`](pkgs/c/compat.websocket.lua) (IXWebSocket 12.0.1 — a pure RFC 6455 client compiled from upstream's `IXWEBSOCKET_SOURCES` minus the four server TUs, so the **base build has zero external dependencies**: TLS off (the OpenSSL/MbedTLS/AppleSSL TUs aren't built) and `IXWEBSOCKET_USE_ZLIB` unset, so the gzip codec compiles to a no-op. Two optional features add on top: `server` (the four server TUs — `IXWebSocketServer`, `IXSocketServer`, `IXHttpServer`, `IXWebSocketProxyServer` — needing nothing external, and it **implies `zlib`** because upstream's server advertises permessage-deflate by default, which the transport negotiates regardless of the define) and `zlib` (deps `compat.zlib` and turns the codec into real per-message-deflate compression). The default-feature test brings its own minimal RFC 6455 echo server on loopback sockets (handshake, masking, fragmentation and close all exercised offline); a second member, `websocket-features`, runs a real `ix::WebSocketServer` and asserts the compression is observable on the wire — a 64 KiB repeated payload round-trips with `wireSize` = 80) | | header-only (with `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | -| header-only, nothing to gate | [`compat.CLI11`](pkgs/c/compat.CLI11.lua) (a command line parser whose every definition is `CLI11_INLINE`, so the package is `*/include` plus an anchor TU. Upstream's two extras stay out: `src/Precompile.cpp` only means anything when `CLI11_COMPILE` also reaches the CONSUMER's translation units — an interface define, not a sources-only gate — and `src/modules/CLI11.cppm` is a module layer, which is a package shape of its own rather than a feature of the compat package) | +| header-only, nothing to gate | [`compat.CLI11`](pkgs/c/compat.CLI11.lua) (a command line parser whose every definition is `CLI11_INLINE`, so the package is `*/include` plus an anchor TU. Upstream's two extras stay out: `src/Precompile.cpp` only means anything when `CLI11_COMPILE` also reaches the CONSUMER's translation units — an interface define, not a sources-only gate — and `src/modules/CLI11.cppm` is a module layer, which is a package shape of its own rather than a feature of the compat package) · [`compat.gtl`](pkgs/c/compat.gtl.lua) (Greg's Template Library — the Swiss-table `flat_hash_map` family plus btrees and a bit_vector. `*/include` exactly, not the tarball root: `tests/` and `examples/` carry headers of their own, and naming `include/` is what upstream's INTERFACE target exposes, so a consumer cannot accidentally resolve into test code) · [`compat.plf-hive`](pkgs/c/compat.plf-hive.lua) (the reference implementation of the proposed `std::hive`; the whole library is one file at the tarball root, so `*` plus an anchor TU is the entire package. Untagged upstream, so the version is a DATE over a commit archive — the compat.khrplatform precedent) | +| single-header library + a GENERATED implementation TU | [`compat.nanosvg`](pkgs/c/compat.nanosvg.lua) (two stb-style headers where the implementation hides behind `NANOSVG_IMPLEMENTATION` / `NANOSVGRAST_IMPLEMENTATION`. Upstream ships no `.c` — its examples define the macros inline — so the package generates one that instantiates BOTH halves once. That is what turns a header drop into something linkable, and it moves the duplicate-symbol hazard from every consumer to a single place: consumers must NOT define those macros again, and the test links `nsvgParse` and `nsvgRasterize` together precisely so a package that instantiated only one half fails here rather than downstream) · [`compat.vulkan-memory-allocator`](pkgs/c/compat.vulkan-memory-allocator.lua) (VMA 3.4.0, same shape but the generated TU also has to make a POLICY choice. VMA defaults to `VMA_STATIC_VULKAN_FUNCTIONS 1`, which references `vkBindBufferMemory2` and seven siblings by name — eight undefined symbols against a headers-only dep. Pulling `compat.vulkan` to satisfy them would force a Vulkan loader on every consumer of a memory allocator and fight anyone dispatching through volk, so the generated TU selects the dynamic path instead and VMA resolves everything through `VmaVulkanFunctions`. Note the implementation is C++ despite the C-shaped API, so the generated file is `.cpp`) | | Runtime loader compat (pure sources, sidestepping upstream codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua) (the Khronos loader: `loader/generated/` is checked in, and the assembly path degrades to plain C through `UNKNOWN_FUNCTIONS_SUPPORTED`, so no CMake/Python/assembler is needed; windows deferred) · [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) | | Whole-source direct build + generated config (only where a platform lacks one) | [`compat.curl`](pkgs/c/compat.curl.lua) (win32 uses upstream's checked-in config, unix generates one) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua) (win/mac use upstream's checked-in config; linux generates one and enables X11 by hand) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua) (91 TUs; the release tarball already ships `ares_build.h` and a Windows config, so only `ares_config.h` is snapshotted per OS) · [`compat.msdfgen`](pkgs/c/compat.msdfgen.lua) (msdfgen 1.13 — the config is not optional here: `core/base.h` opens with `#include `, so without generating it nothing compiles, not even `core/`. Generating it rather than passing `-D` flags is also what makes the library and its consumers agree BY CONSTRUCTION — `base.h` is reached from every public header, so the file is the single place that says which of SVG/PNG/Skia exist. Of the four `ext/` units only `import-font.cpp` is built; the other three each need a library this index does not carry, and their declarations disappear through the same generated config. `MSDFGEN_USE_CPP11` is left off on purpose: it adds move constructors to `Bitmap`, so it changes the layout of a type that crosses the library boundary, and a package cannot guarantee every consumer defines it identically) | -| Upstream amalgamation (one TU is the whole library) | [`compat.harfbuzz`](pkgs/c/compat.harfbuzz.lua) (HarfBuzz 14.3.0 — upstream builds with meson, and reproducing that here would mean tracking ~137 `.cc` files plus a generated config. `src/harfbuzz.cc` is upstream's own supported "compile one file" path, so `sources` is a single line that cannot drift out of sync with a release. The amalgamation also `#include`s the CoreText/DirectWrite/GDI/GLib/Graphite2 backends, each behind its own `HAVE_*` gate, so naming only `HAVE_FREETYPE` selects the FreeType bridge and compiles the rest to nothing. `HB_NO_MT` is deliberately NOT set: it removes HarfBuzz's atomics, which is only sound under a single-threading promise a shared package cannot make for its consumers) · [`compat.mimalloc`](pkgs/c/compat.mimalloc.lua) (mimalloc 3.4.5 — the opposite lesson: it also ships an amalgamation (`src/static.c`), and using it would be wrong. A `src/*.c` glob is wrong three ways, each a LINK error rather than a compile error — `static.c` duplicates every symbol, and `free.c`/`alloc-override.c` are `#include`d by `alloc.c` rather than being TUs — so the source list is upstream's own `mi_sources`. `MI_MALLOC_OVERRIDE` stays off: a dependency silently taking over the process allocator is not a package's call) | +| Upstream amalgamation (one TU is the whole library) | [`compat.harfbuzz`](pkgs/c/compat.harfbuzz.lua) (HarfBuzz 14.3.0 — upstream builds with meson, and reproducing that here would mean tracking ~137 `.cc` files plus a generated config. `src/harfbuzz.cc` is upstream's own supported "compile one file" path, so `sources` is a single line that cannot drift out of sync with a release. The amalgamation also `#include`s the CoreText/DirectWrite/GDI/GLib/Graphite2 backends, each behind its own `HAVE_*` gate, so naming only `HAVE_FREETYPE` selects the FreeType bridge and compiles the rest to nothing. `HB_NO_MT` is deliberately NOT set: it removes HarfBuzz's atomics, which is only sound under a single-threading promise a shared package cannot make for its consumers) · [`compat.mimalloc`](pkgs/c/compat.mimalloc.lua) (mimalloc 3.4.5 — the opposite lesson: it also ships an amalgamation (`src/static.c`), and using it would be wrong. A `src/*.c` glob is wrong three ways, each a LINK error rather than a compile error — `static.c` duplicates every symbol, and `free.c`/`alloc-override.c` are `#include`d by `alloc.c` rather than being TUs — so the source list is upstream's own `mi_sources`. `MI_MALLOC_OVERRIDE` stays off: a dependency silently taking over the process allocator is not a package's call) · [`compat.miniaudio`](pkgs/c/compat.miniaudio.lua) (miniaudio 0.11.25 — `miniaudio.c` is upstream's own two-line `MINIAUDIO_IMPLEMENTATION` driver and its CMake library target, so `sources` is one line that tracks the release. The Linux link line is `-ldl -lpthread -lm` and deliberately NOT `-lasound`/`-lpulse`: miniaudio `dlopen`s its backends, so the package builds on a machine that has neither) · [`compat.spirv-reflect`](pkgs/c/compat.spirv-reflect.lua) (Khronos' SPIR-V reflection library; `spirv_reflect.c` is exactly upstream's `spirv-reflect-static` target. Both `*` and `*/include` are exposed so the default `"./include/spirv/unified1/spirv.h"` and the `SPIRV_REFLECT_USE_SYSTEM_SPIRV_H` spelling resolve to the SAME bundled grammar header — a consumer that defines that macro cannot silently get a different SPIR-V revision than this `.c` was written against. Versioned by SDK line to stay in step with compat.vulkan-headers) | | Upstream codegen frozen into the mirror archive | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua) (two versions: `4.5.0` = the `godot-4.5-stable` bindings, `10.0.0-rc1` = godot-cpp's own 10.x line, whose bindings target Godot 4.6. The ~1000 GDExtension classes under `gen/` exist in no upstream tag archive — upstream's `binding_generator.py` emits them at build time. Running it once offline and publishing upstream's tree byte-for-byte **plus** `gen/` keeps Python off the consumer side entirely; `tools/godot-cpp/repack.sh` reproduces the archive deterministically and refuses to publish if any upstream file differs) | | Header package filling a gap in the index | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua) (libglvnd's `GL/glx.h`, absent from the Khronos registry and required by SDL's X11 backend) | | C++ application framework compat (dependencies reuse packages already in the index) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua) (upstream's `3rd/` ships 8 vendored dependencies; none of them is compiled here — all are redirected to the same-version `compat.*` packages in this index) | diff --git a/README.zh-CN.md b/README.zh-CN.md index a4a43a1d..09d905dc 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -39,10 +39,11 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | C++ 源码 compat(彼此依赖) | [`compat.abseil`](pkgs/c/compat.abseil.lua)(151 TU;对 `absl/**` 取通配后,按上游自身的 test/benchmark 命名约定裁剪) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua)(libprotobuf 运行时,79 TU 逐条转录自上游 `src/file_lists.cmake`;因 protobuf 公开头文件 include 了 `absl/…`,故显式依赖 `compat.abseil`;`gzip` feature 定义 `HAVE_ZLIB` 并拉入 `compat.zlib`,`upb` feature 则从同一个 tarball 里再编出 protobuf 的 64 TU C 运行时;还以 `kind = "bin"` target 暴露 **`protoc`**,消费者写 `tools = ["protoc"]` 即可从「自己链接的那个包」拿到为本机构建的编译器,使生成器与运行时的版本错配无法表达) · [`compat.re2`](pkgs/c/compat.re2.lua)(22 TU,取自上游自身的 `RE2_SOURCES`) · [`compat.redis-plus-plus`](pkgs/c/compat.redis-plus-plus.lua)(redis++ 1.3.13 —— 同步客户端,17 TU + `patterns/redlock.cpp`,依赖 `compat.hiredis`;CMake 唯一会生成的头 `hiredis_features.h` 用 `generated_files` 快照,async/TLS TU 不收,基座保持两包成对。`async` feature 补齐 libuv 版 `AsyncRedis` 接口(9 个 async TU + `compat.libuv`;`event_loop.cpp` 在后台线程跑 `uv_run`,`` 经 compat.hiredis 的包装头到达)。两个版本分处源码结构分水岭两侧,共享同一份源列表:1.3.13(现代 17-TU 布局)与 1.3.3(缺 `redis_uri.cpp`/`redlock` 的 15-TU 旧布局)—— 并集之所以成立,是因为 1.3.3 的 TU 是 1.3.13 的严格子集,恰好两个 glob 在 1.3.3 上零命中(仅警告,非错误;与 compat.catch2 同款手法)) | | C++ 源码 compat(零依赖客户端 + 可选组件) | [`compat.websocket`](pkgs/c/compat.websocket.lua)(IXWebSocket 12.0.1 —— 从上游 `IXWEBSOCKET_SOURCES` 剔掉 4 个 server TU 后直编的纯 RFC 6455 客户端,**基座零外部依赖**:TLS 关闭(OpenSSL/MbedTLS/AppleSSL 三组 TU 均不编),`IXWEBSOCKET_USE_ZLIB` 不定义(gzip codec 编译为 no-op)。两个可选 feature 在基座上叠加:`server`(4 个 server TU —— `IXWebSocketServer`/`IXSocketServer`/`IXHttpServer`/`IXWebSocketProxyServer`,零新增外部依赖,且 **implies `zlib`** —— 因为上游 server 默认就宣称 permessage-deflate,而 transport 的协商不受宏门控)与 `zlib`(依赖 `compat.zlib`,把 codec 变成真正的 permessage-deflate 压缩)。默认构建的测试自带基于 loopback 原始 socket 的最小 RFC 6455 echo server(握手/掩码/分片/关闭全部离线实测);第二个成员 `websocket-features` 跑真实的 `ix::WebSocketServer`,并断言压缩在线路上可观测 —— 64 KiB 重复载荷往返,`wireSize` = 80) | | header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | -| header-only(无可门控组件) | [`compat.CLI11`](pkgs/c/compat.CLI11.lua)(命令行解析器,全部定义都是 `CLI11_INLINE`,故整包就是 `*/include` 加一个 anchor TU。上游两个额外件都不收:`src/Precompile.cpp` 只有在 `CLI11_COMPILE` 同时到达**消费者** TU 时才有意义 —— 那是 interface define,不是 sources 门控;`src/modules/CLI11.cppm` 属于模块层,是另一种包形态,而非 compat 包的 feature) | +| header-only(无可门控组件) | [`compat.CLI11`](pkgs/c/compat.CLI11.lua)(命令行解析器,全部定义都是 `CLI11_INLINE`,故整包就是 `*/include` 加一个 anchor TU。上游两个额外件都不收:`src/Precompile.cpp` 只有在 `CLI11_COMPILE` 同时到达**消费者** TU 时才有意义 —— 那是 interface define,不是 sources 门控;`src/modules/CLI11.cppm` 属于模块层,是另一种包形态,而非 compat 包的 feature) · [`compat.gtl`](pkgs/c/compat.gtl.lua)(Greg's Template Library —— Swiss-table 的 `flat_hash_map` 家族,外加 btree 与 bit_vector。只取 `*/include` 而非 tarball 根:`tests/` 与 `examples/` 各自带头文件,而 `include/` 正是上游 INTERFACE target 暴露的范围,消费者不会误解析到测试代码) · [`compat.plf-hive`](pkgs/c/compat.plf-hive.lua)(提案中 `std::hive` 的参考实现;整库就是 tarball 根下一个文件,故全包即 `*` 加一个 anchor TU。上游不打 tag,于是版本用 commit 归档上的日期 —— 沿用 compat.khrplatform 的先例) | +| 单头库 + **生成**实现 TU | [`compat.nanosvg`](pkgs/c/compat.nanosvg.lua)(两个 stb 风格头文件,实现藏在 `NANOSVG_IMPLEMENTATION` / `NANOSVGRAST_IMPLEMENTATION` 之后。上游不提供 `.c` —— 其示例是就地 define 宏 —— 故本包生成一个,把两半各实例化一次。这才让「一堆头文件」变成可链接的包,并把重复符号的风险从每个消费者收敛到唯一一处:消费者不得再次定义这两个宏。测试刻意同时链接 `nsvgParse` 与 `nsvgRasterize`,使「只实例化了一半」的包在此处就失败,而不是流到下游)· [`compat.vulkan-memory-allocator`](pkgs/c/compat.vulkan-memory-allocator.lua)(VMA 3.4.0,同一形态,但生成的 TU 还得做一个**策略**选择。VMA 默认 `VMA_STATIC_VULKAN_FUNCTIONS 1`,会按名字引用 `vkBindBufferMemory2` 等八个符号 —— 对「只依赖头文件」而言就是八个未定义引用。为此拉入 `compat.vulkan` 是错的:那会逼所有用内存分配器的消费者都链上 Vulkan loader,并与通过 volk 自行分发的项目冲突。故生成的 TU 改走动态路径,VMA 一律经 `VmaVulkanFunctions` 解析。注意其 API 形如 C 但实现是 C++,故生成文件为 `.cpp`)| | 运行时 loader compat(纯源码,绕开上游 codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua)(Khronos loader:`loader/generated/` 已签入,汇编路径经 `UNKNOWN_FUNCTIONS_SUPPORTED` 降级为纯 C,故无需 CMake/Python/汇编器;windows 延后)· [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) | | 全源码直编 + 生成 config(仅缺口平台) | [`compat.curl`](pkgs/c/compat.curl.lua)(win32 用上游签入 config,unix 生成) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua)(win/mac 用上游签入 config,linux 生成 + 手工开 X11) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua)(91 TU;release tarball 已自带 `ares_build.h` 与 Windows 配置,故只需按 OS 冻结 `ares_config.h`) · [`compat.msdfgen`](pkgs/c/compat.msdfgen.lua)(msdfgen 1.13 —— 这里的 config 不是可选项:`core/base.h` 开头就是 `#include `,不生成它连 `core/` 都编不了。选择生成它而非传 `-D`,还使库与消费者**天然一致** —— `base.h` 被每个公开头间接包含,于是该文件成为「SVG/PNG/Skia 哪些存在」的唯一出处。`ext/` 四个单元只编 `import-font.cpp`,其余三个各需一个本索引没有的库,其声明经由同一份生成 config 一并消失。`MSDFGEN_USE_CPP11` 刻意不开:它给 `Bitmap` 增加移动构造,即改变了跨库边界类型的布局,而包无法保证每个消费者都同样定义它) | -| 上游 amalgamation(单 TU 即整库) | [`compat.harfbuzz`](pkgs/c/compat.harfbuzz.lua)(HarfBuzz 14.3.0 —— 上游用 meson,在此复刻意味着跟踪 ~137 个 `.cc` 加一份生成的 config。`src/harfbuzz.cc` 正是上游自己支持的「只编一个文件」路径,于是 `sources` 只有一行,且不会与 release 脱节。该 amalgamation 同时 `#include` 了 CoreText/DirectWrite/GDI/GLib/Graphite2 各后端,每个都有自己的 `HAVE_*` 门,因此仅声明 `HAVE_FREETYPE` 即可精确选中 FreeType 桥接,其余编译为空。`HB_NO_MT` 刻意不设:它会去掉 HarfBuzz 的原子操作与锁,仅在消费者保证单线程时才成立,而共享包无法替消费者作此承诺)· [`compat.mimalloc`](pkgs/c/compat.mimalloc.lua)(mimalloc 3.4.5 —— 反向的教训:它同样带 amalgamation(`src/static.c`),但用它是错的。`src/*.c` 通配会在三处出错,且每一处都是**链接期**而非编译期报错 —— `static.c` 会让每个符号重复,`free.c` / `alloc-override.c` 则是被 `alloc.c` `#include` 的、并非独立 TU —— 故源列表取上游自己的 `mi_sources`。`MI_MALLOC_OVERRIDE` 保持关闭:让一个依赖悄悄接管进程分配器,不该由包来决定)| +| 上游 amalgamation(单 TU 即整库) | [`compat.harfbuzz`](pkgs/c/compat.harfbuzz.lua)(HarfBuzz 14.3.0 —— 上游用 meson,在此复刻意味着跟踪 ~137 个 `.cc` 加一份生成的 config。`src/harfbuzz.cc` 正是上游自己支持的「只编一个文件」路径,于是 `sources` 只有一行,且不会与 release 脱节。该 amalgamation 同时 `#include` 了 CoreText/DirectWrite/GDI/GLib/Graphite2 各后端,每个都有自己的 `HAVE_*` 门,因此仅声明 `HAVE_FREETYPE` 即可精确选中 FreeType 桥接,其余编译为空。`HB_NO_MT` 刻意不设:它会去掉 HarfBuzz 的原子操作与锁,仅在消费者保证单线程时才成立,而共享包无法替消费者作此承诺)· [`compat.mimalloc`](pkgs/c/compat.mimalloc.lua)(mimalloc 3.4.5 —— 反向的教训:它同样带 amalgamation(`src/static.c`),但用它是错的。`src/*.c` 通配会在三处出错,且每一处都是**链接期**而非编译期报错 —— `static.c` 会让每个符号重复,`free.c` / `alloc-override.c` 则是被 `alloc.c` `#include` 的、并非独立 TU —— 故源列表取上游自己的 `mi_sources`。`MI_MALLOC_OVERRIDE` 保持关闭:让一个依赖悄悄接管进程分配器,不该由包来决定) · [`compat.miniaudio`](pkgs/c/compat.miniaudio.lua)(miniaudio 0.11.25 —— `miniaudio.c` 就是上游自己那两行 `MINIAUDIO_IMPLEMENTATION` 驱动文件,也是其 CMake 库目标,故 `sources` 只有一行且随 release 走。Linux 链接行是 `-ldl -lpthread -lm`,刻意不含 `-lasound`/`-lpulse`:miniaudio 用 `dlopen` 加载后端,因此在两者都没有的机器上依然能构建)· [`compat.spirv-reflect`](pkgs/c/compat.spirv-reflect.lua)(Khronos 的 SPIR-V 反射库;`spirv_reflect.c` 恰是上游 `spirv-reflect-static` 目标。同时暴露 `*` 与 `*/include`,使默认的 `"./include/spirv/unified1/spirv.h"` 与 `SPIRV_REFLECT_USE_SYSTEM_SPIRV_H` 两种写法解析到**同一份**内置语法头 —— 定义了该宏的消费者不会悄悄拿到与这份 `.c` 不匹配的 SPIR-V 修订。版本按 SDK 线号,与 compat.vulkan-headers 保持同步) | | 上游 codegen 前置冻结进镜像归档 | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua)(两个版本:`4.5.0` 是 `godot-4.5-stable` 的绑定,`10.0.0-rc1` 是 godot-cpp 自己的 10.x 线、对应 Godot 4.6。`gen/` 下约 1000 个 GDExtension 类不在任何上游 tag 归档里,由上游 `binding_generator.py` 在构建时生成。改为离线跑一次,把上游源码树逐字节原样 **加上** `gen/` 一起发布,消费侧就完全不需要 Python;`tools/godot-cpp/repack.sh` 可确定性复现该归档,且上游文件一旦有出入即拒绝打包) | | 补索引空缺的头文件包 | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua)(libglvnd 的 `GL/glx.h`,Khronos registry 不含,SDL 的 X11 后端必需) | | C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) | diff --git a/docs/superpowers/specs/2026-08-12-add-xrgui-header-libraries-plan.md b/docs/superpowers/specs/2026-08-12-add-xrgui-header-libraries-plan.md new file mode 100644 index 00000000..59633e5b --- /dev/null +++ b/docs/superpowers/specs/2026-08-12-add-xrgui-header-libraries-plan.md @@ -0,0 +1,141 @@ +# Adding the six remaining XRGUI dependencies to mcpp-index + +Date: 2026-08-12 + +## Why these six + +[PR #206](https://github.com/mcpplibs/mcpp-index/pull/206) added the three +*compiled* libraries XRGUI ([Sunrisepeak/xrgui#1](https://github.com/Sunrisepeak/xrgui/pull/1)) +was missing — harfbuzz, msdfgen, mimalloc. What remained were six libraries the +project was reaching through **git submodules and `-I` flags** instead of through +a package manager. + +That turned out to be worth fixing for a reason beyond XRGUI. Reading its +`xmake.lua` shows line 44: + +```lua +add_requires("nanosvg", "spirv-reflect", "gtl", "glfw", "miniaudio") +``` + +The xmake build gets four of these from **xrepo packages**. The `external/gtl`, +`external/nanosvg`, `external/miniaudio` and `external/spirv_reflect` submodules +it also carries are not what that build compiles against — they are vestigial. +So packaging them is not an mcpp-specific accommodation; it is catching mcpp up +to what the project's own primary build already does. + +All six stand on their own merits, independent of XRGUI: + +| package | upstream | why it belongs in a general index | +|---|---|---| +| `compat.miniaudio` | mackron | the single-file audio library, ~4k stars | +| `compat.spirv-reflect` | **KhronosGroup** | official SPIR-V reflection | +| `compat.nanosvg` | memononen | the small SVG parser, ~2.5k stars | +| `compat.vulkan-memory-allocator` | **GPUOpen/AMD** | the standard Vulkan allocator | +| `compat.gtl` | greg7mdp | Swiss-table hash maps (parallel-hashmap's successor) | +| `compat.plf-hive` | mattreecebentley | reference implementation of proposed `std::hive` | + +Not packaged, deliberately: `small_vector`, `allocator2d`, `mo_yanxi_utility`, +`mo_yanxi_vulkan_wrapper` and `mo_yanxi_react_flow` are the XRGUI author's own +code. They are the project, not its dependencies, and they belong in the project's +own tree. + +## Shapes + +Three distinct shapes, and the middle one is new to this index. + +### A — header-only + anchor TU (`gtl`, `plf-hive`) + +The established shape (compat.eigen, compat.CLI11): expose the headers, carry a +trivial TU so mcpp has a buildable `lib` target. + +The only judgement is *which* directory to expose. `gtl` gets `*/include`, not +the tarball root, because `tests/` and `examples/` carry headers of their own and +`include/` is exactly what upstream's INTERFACE target publishes. `plf-hive` gets +`*` because the entire library is one file sitting at the root. + +### B — single-header library + a GENERATED implementation TU (`nanosvg`, `vulkan-memory-allocator`) + +Both are stb-style: the header holds the implementation behind a macro, and +upstream ships no `.c`/`.cpp` to instantiate it. Leaving that to the consumer +would make the package a header drop rather than something you link, and would +hand every consumer the same duplicate-symbol hazard. So the package generates +the implementation TU once. + +The cost is a rule consumers must follow, and it is stated in both descriptors: +**do not define the implementation macro again** — the failure is a link error, +so it surfaces late. + +`vulkan-memory-allocator` additionally forced a *policy* choice. VMA defaults to +`VMA_STATIC_VULKAN_FUNCTIONS 1`, which references `vkBindBufferMemory2`, +`vkGetPhysicalDeviceProperties2` and six more **by name**. Against a headers-only +dependency that is eight undefined symbols — observed, not predicted: + +``` +vk_mem_alloc.h:13600: undefined reference to `vkGetBufferMemoryRequirements2' +vk_mem_alloc.h:13601: undefined reference to `vkGetImageMemoryRequirements2' +… six more +``` + +Adding `compat.vulkan` would have made that link, and would have been the wrong +fix: it forces a Vulkan loader on every consumer of a *memory allocator*, and it +fights consumers that dispatch through volk or their own device table. The +generated TU selects `VMA_DYNAMIC_VULKAN_FUNCTIONS` instead, so VMA resolves +every entry point through `VmaVulkanFunctions` and the package links against the +Vulkan **headers** alone. + +### C — one upstream TU is the whole library (`miniaudio`, `spirv-reflect`) + +`miniaudio.c` is upstream's own two-line `MINIAUDIO_IMPLEMENTATION` driver and +its CMake library target. `spirv_reflect.c` is exactly upstream's +`spirv-reflect-static` target. In both cases `sources` is one line that cannot +drift from a release. + +Two details worth recording: + +- miniaudio's Linux link line is `-ldl -lpthread -lm` and deliberately **not** + `-lasound` / `-lpulse`. miniaudio `dlopen`s its backends, so hard-linking them + would break the package on a machine that has neither, for no gain. +- spirv-reflect exposes **both** `*` and `*/include`. `spirv_reflect.h:35-37` + chooses between `` and the bundled + `"./include/spirv/unified1/spirv.h"` depending on + `SPIRV_REFLECT_USE_SYSTEM_SPIRV_H`. Exposing both makes the two spellings + resolve to the *same* bundled grammar header, so a consumer that defines that + macro cannot silently get a different SPIR-V revision than this `.c` was + written against. + +## Versioning the two untagged libraries + +`nanosvg` and `plf_hive` cut no tags and publish no releases. Following the +`compat.khrplatform` precedent (which mirrors the untagged EGL-Registry), each +pins a **commit archive** under a **date version** taken from that commit's date: +`nanosvg = 2026.07.09`, `plf-hive = 2026.07.31`. Date keys sort correctly against +any future snapshot. + +`spirv-reflect` has the opposite problem — it tags in lockstep with the Vulkan +SDK (`vulkan-sdk-1.4.357.0`). The version key drops the prefix so it sorts +numerically and lines up with `compat.vulkan-headers` / `compat.vulkan` of the +same SDK. Keep the three moving together. + +## Tests: what each one would catch + +Every test asserts *behaviour*. A stub that linked but did nothing fails all six. + +| test | the property it pins down | +|---|---| +| `plf-hive/hive.cpp` | element **addresses survive** erasing their neighbours and refilling — the guarantee that distinguishes a hive from a vector | +| `gtl/containers.cpp` | 1000 entries survive rehashing; `btree_set` iterates **in order** and `lower_bound` lands correctly | +| `nanosvg/parse.cpp` | parsed geometry matches the declared rect; the rasterized centre pixel is opaque red, the corner is untouched, and coverage is 2300–2700 px for a 50×50 rect. Links `nsvgParse` **and** `nsvgRasterize`, so a package that instantiated only one half fails here | +| `miniaudio/roundtrip.cpp` | encode → decode a 480 Hz sine and compare samples (`< 1e-5`), **plus** a peak check so silence cannot pass vacuously. Device-free by design: CI has no sound card | +| `spirv-reflect/reflect.cpp` | reflects a **real** glslc-compiled compute shader (embedded as words, no compiler needed at test time) and checks set 0/binding 3 storage, set 1/binding 0 uniform, and a 64-byte push block. Plus a negative case: bad magic must be **rejected**, or a always-succeed stub would pass everything above | +| `vulkan-memory-allocator/virtual_block.cpp` | the virtual allocator — 16 allocations, offsets honour alignment, **no two overlap**, stats match, freeing returns the space, and an over-sized request is refused. The one part of VMA assertable with no GPU | + +## Verification performed locally + +- All six `mcpp test` runs pass (`1 passed; 0 failed` each). +- `check_mirror_urls` / `check_package_name` / `check_platform_version_parity` + pass on all six; `check_cross_package_refs` passes across all 96 descriptors. +- `mcpp xpkg parse` passes for **all 96** descriptors using the *pinned* CI + version 2026.8.10.3, downloaded for the purpose — not the newer local build, + because that check is what enforces "floor first, new grammar after". +- All six CN mirrors return HTTP 200 and are **byte-identical** to GLOBAL, + which is what `mirror-cn-reachable` verifies. diff --git a/mcpp.toml b/mcpp.toml index df2c7b41..d6f42d04 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -84,6 +84,12 @@ members = [ "tests/examples/mimalloc", "tests/examples/harfbuzz", "tests/examples/msdfgen", + "tests/examples/gtl", + "tests/examples/miniaudio", + "tests/examples/nanosvg", + "tests/examples/plf-hive", + "tests/examples/spirv-reflect", + "tests/examples/vulkan-memory-allocator", ] # ── The index redirect, hoisted to the workspace root ─────────────────── diff --git a/pkgs/c/compat.gtl.lua b/pkgs/c/compat.gtl.lua new file mode 100644 index 00000000..bf3ff448 --- /dev/null +++ b/pkgs/c/compat.gtl.lua @@ -0,0 +1,76 @@ +-- Form B inline descriptor for gtl (Greg's Template Library) — a C++20 header +-- library whose best-known members are the `flat_hash_map` / `parallel_flat_hash_map` +-- family (the successor to the author's `parallel-hashmap`, itself derived from +-- Abseil's Swiss tables) plus btree containers, a bit_vector, an LRU cache, and +-- assorted vector/utility bits. +-- +-- HEADER-ONLY: every header lives under `include/gtl/`, so exposing `include/` +-- is the whole build and consumers write `#include `. A trivial +-- anchor TU gives mcpp a buildable `lib` target (same shape as compat.eigen / +-- compat.opengl / compat.khrplatform). +-- +-- Why `*/include` and not `*`: the tarball root also carries `tests/` and +-- `examples/`, and several of those directories contain headers of their own. +-- Naming `include/` exactly is what upstream's CMake target does +-- (`target_include_directories(gtl INTERFACE include)`), so a consumer sees the +-- same header set here as under CMake — no accidental resolution into test code. +-- +-- LANGUAGE FLOOR: gtl requires C++20 and says so (`#error "gtl requires C++20 or +-- later"` in bits.hpp). `language = "c++23"` here is the package's own compile +-- setting for the anchor TU; the headers themselves are compiled in the +-- consumer's mode, which must be C++20 or newer. +package = { + spec = "1", + namespace = "compat", + name = "gtl", + description = "Greg's Template Library — C++20 hash maps, btrees, bit vectors and utilities (header-only)", + licenses = {"Apache-2.0"}, + repo = "https://github.com/greg7mdp/gtl", + type = "package", + + xpm = { + linux = { + ["1.2.0"] = { + url = { + GLOBAL = "https://github.com/greg7mdp/gtl/archive/refs/tags/v1.2.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/gtl/releases/download/1.2.0/gtl-1.2.0.tar.gz", + }, + sha256 = "1547ab78f62725c380f50972f7a49ffd3671ded17a3cb34305da5c953c6ba8e7", + }, + }, + macosx = { + ["1.2.0"] = { + url = { + GLOBAL = "https://github.com/greg7mdp/gtl/archive/refs/tags/v1.2.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/gtl/releases/download/1.2.0/gtl-1.2.0.tar.gz", + }, + sha256 = "1547ab78f62725c380f50972f7a49ffd3671ded17a3cb34305da5c953c6ba8e7", + }, + }, + windows = { + ["1.2.0"] = { + url = { + GLOBAL = "https://github.com/greg7mdp/gtl/archive/refs/tags/v1.2.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/gtl/releases/download/1.2.0/gtl-1.2.0.tar.gz", + }, + sha256 = "1547ab78f62725c380f50972f7a49ffd3671ded17a3cb34305da5c953c6ba8e7", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + -- Exactly what upstream's INTERFACE target exposes: ``. + include_dirs = { "*/include" }, + generated_files = { + ["mcpp_generated/gtl_anchor.c"] = [==[ +int mcpp_compat_gtl_headers_anchor(void) { return 0; } +]==], + }, + sources = { "mcpp_generated/gtl_anchor.c" }, + targets = { ["gtl"] = { kind = "lib" } }, + deps = { }, + }, +} diff --git a/pkgs/c/compat.miniaudio.lua b/pkgs/c/compat.miniaudio.lua new file mode 100644 index 00000000..c83288bd --- /dev/null +++ b/pkgs/c/compat.miniaudio.lua @@ -0,0 +1,85 @@ +-- Form B inline descriptor for miniaudio — a single-file audio playback and +-- capture library that wraps every platform's native backend (WASAPI/DirectSound/ +-- WinMM, Core Audio, ALSA/PulseAudio/JACK, OSS, AAudio/OpenSL, Web Audio) behind +-- one API, plus decoding for WAV/FLAC/MP3 and a mixing/resampling engine. +-- +-- SHAPE: header-only WITH an upstream-supplied implementation TU. `miniaudio.h` +-- carries both the declarations and, behind MINIAUDIO_IMPLEMENTATION, the whole +-- implementation; `miniaudio.c` at the tarball root is upstream's own two-line +-- driver for it: +-- +-- #define MINIAUDIO_IMPLEMENTATION +-- #include "miniaudio.h" +-- +-- Compiling that file here is what makes this a linkable package rather than a +-- pile of headers, and it is upstream's supported way to do it (CMakeLists.txt +-- builds the `miniaudio` library target from exactly this source). +-- +-- CONSEQUENCE FOR CONSUMERS: do NOT also define MINIAUDIO_IMPLEMENTATION in your +-- own code. The implementation is already compiled into this package's lib, and +-- defining the macro a second time gives you every miniaudio symbol twice -- +-- a LINK error, not a compile error, so it surfaces late. Just +-- `#include ` and link. +-- +-- SYSTEM LIBRARIES: miniaudio dlopen()s its backends rather than linking them, +-- which is why the Linux link line is `-ldl -lpthread -lm` and NOT -lasound / +-- -lpulse -- ALSA and PulseAudio are resolved at runtime if present, so the +-- package builds on a machine that has neither. (CMakeLists.txt: THREADS, +-- ${CMAKE_DL_LIBS}, m.) +package = { + spec = "1", + namespace = "compat", + name = "miniaudio", + description = "Single-file audio playback and capture library with native backends and WAV/FLAC/MP3 decoding", + licenses = {"MIT-0", "Unlicense"}, + repo = "https://github.com/mackron/miniaudio", + type = "package", + + xpm = { + linux = { + ["0.11.25"] = { + url = { + GLOBAL = "https://github.com/mackron/miniaudio/archive/refs/tags/0.11.25.tar.gz", + CN = "https://gitcode.com/mcpp-res/miniaudio/releases/download/0.11.25/miniaudio-0.11.25.tar.gz", + }, + sha256 = "b900edcffe979816e2560a0580b9b1216d674b4f17fbadeca8f777a7f8ab0274", + }, + }, + macosx = { + ["0.11.25"] = { + url = { + GLOBAL = "https://github.com/mackron/miniaudio/archive/refs/tags/0.11.25.tar.gz", + CN = "https://gitcode.com/mcpp-res/miniaudio/releases/download/0.11.25/miniaudio-0.11.25.tar.gz", + }, + sha256 = "b900edcffe979816e2560a0580b9b1216d674b4f17fbadeca8f777a7f8ab0274", + }, + }, + windows = { + ["0.11.25"] = { + url = { + GLOBAL = "https://github.com/mackron/miniaudio/archive/refs/tags/0.11.25.tar.gz", + CN = "https://gitcode.com/mcpp-res/miniaudio/releases/download/0.11.25/miniaudio-0.11.25.tar.gz", + }, + sha256 = "b900edcffe979816e2560a0580b9b1216d674b4f17fbadeca8f777a7f8ab0274", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + -- Tarball root: `#include `. + include_dirs = { "*" }, + sources = { "*/miniaudio.c" }, + targets = { ["miniaudio"] = { kind = "lib" } }, + deps = { }, + linux = { + -- Backends are dlopen()ed, so no -lasound / -lpulse here. + ldflags = { "-ldl", "-lpthread", "-lm" }, + }, + macosx = { + ldflags = { "-lpthread", "-lm" }, + }, + }, +} diff --git a/pkgs/c/compat.nanosvg.lua b/pkgs/c/compat.nanosvg.lua new file mode 100644 index 00000000..22e0d4e7 --- /dev/null +++ b/pkgs/c/compat.nanosvg.lua @@ -0,0 +1,95 @@ +-- Form B inline descriptor for NanoSVG — a small SVG parser that flattens paths +-- to cubic beziers, plus an optional scanline rasterizer. Two headers, no +-- dependencies; the usual reason to reach for it is turning icon SVGs into +-- geometry or bitmaps at load time. +-- +-- SHAPE: two single-file libraries in the stb tradition. `src/nanosvg.h` and +-- `src/nanosvgrast.h` each hold declarations plus, behind +-- NANOSVG_IMPLEMENTATION / NANOSVGRAST_IMPLEMENTATION, the implementation. +-- Unlike miniaudio, upstream ships NO implementation .c -- its own examples +-- define the macros inline -- so this package generates one. That is what makes +-- it a linkable package instead of a pile of headers, and it means a consumer +-- writes only `#include ` and links. +-- +-- CONSEQUENCE FOR CONSUMERS: do NOT also define NANOSVG_IMPLEMENTATION or +-- NANOSVGRAST_IMPLEMENTATION. Both implementations are already compiled into +-- this package's lib; defining either macro again duplicates every symbol, +-- which is a LINK error and therefore surfaces late. +-- +-- INCLUDE SPELLING: `include_dirs = { "*/src" }`, so the include is +-- `` / `` -- the spelling upstream's own examples and +-- README use. Note some package managers install these under a `nanosvg/` +-- subdirectory, making the include read ``; that nesting is +-- not upstream's layout and is not reproduced here. +-- +-- VERSIONING: upstream cuts no tags and publishes no releases -- development is +-- a linear series of commits on master. Following compat.khrplatform (which +-- mirrors the untagged EGL-Registry), this pins a commit archive under a DATE +-- version; `2026.07.09` is the commit date of 239e102e. +-- +-- The rasterizer needs no separate feature gate: it is one more header behind +-- the same include dir, and its implementation costs one function that the +-- linker drops when unused. +package = { + spec = "1", + namespace = "compat", + name = "nanosvg", + description = "NanoSVG — small SVG parser that flattens paths to beziers, with an optional rasterizer", + licenses = {"Zlib"}, + repo = "https://github.com/memononen/nanosvg", + type = "package", + + xpm = { + linux = { + ["2026.07.09"] = { + url = { + GLOBAL = "https://github.com/memononen/nanosvg/archive/239e102ec2c691f2902e20ace2ed36ee4a35cfe6.tar.gz", + CN = "https://gitcode.com/mcpp-res/nanosvg/releases/download/2026.07.09/nanosvg-2026.07.09.tar.gz", + }, + sha256 = "2bc68bdb518d7800252042e5cad50a0ab321596f0cbf49ef2a752926329063d2", + }, + }, + macosx = { + ["2026.07.09"] = { + url = { + GLOBAL = "https://github.com/memononen/nanosvg/archive/239e102ec2c691f2902e20ace2ed36ee4a35cfe6.tar.gz", + CN = "https://gitcode.com/mcpp-res/nanosvg/releases/download/2026.07.09/nanosvg-2026.07.09.tar.gz", + }, + sha256 = "2bc68bdb518d7800252042e5cad50a0ab321596f0cbf49ef2a752926329063d2", + }, + }, + windows = { + ["2026.07.09"] = { + url = { + GLOBAL = "https://github.com/memononen/nanosvg/archive/239e102ec2c691f2902e20ace2ed36ee4a35cfe6.tar.gz", + CN = "https://gitcode.com/mcpp-res/nanosvg/releases/download/2026.07.09/nanosvg-2026.07.09.tar.gz", + }, + sha256 = "2bc68bdb518d7800252042e5cad50a0ab321596f0cbf49ef2a752926329063d2", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + -- Upstream's own spelling: ``, ``. + include_dirs = { "*/src" }, + -- Upstream ships no implementation TU, so supply one. Both single-file + -- libraries are instantiated here, once, for the whole package. + generated_files = { + ["mcpp_generated/nanosvg_impl.c"] = [==[ +#define NANOSVG_IMPLEMENTATION +#include + +#define NANOSVGRAST_IMPLEMENTATION +#include +]==], + }, + sources = { "mcpp_generated/nanosvg_impl.c" }, + targets = { ["nanosvg"] = { kind = "lib" } }, + deps = { }, + linux = { ldflags = { "-lm" } }, + macosx = { ldflags = { "-lm" } }, + }, +} diff --git a/pkgs/c/compat.plf-hive.lua b/pkgs/c/compat.plf-hive.lua new file mode 100644 index 00000000..dacfd348 --- /dev/null +++ b/pkgs/c/compat.plf-hive.lua @@ -0,0 +1,79 @@ +-- Form B inline descriptor for plf::hive — the reference implementation of the +-- container proposed for the standard as `std::hive` (P0447). A hive is an +-- unordered sequence container with stable element addresses across insert and +-- erase, no reallocation, and O(1) erasure — the shape you want for the "pool of +-- long-lived objects that are constantly created and destroyed and pointed at" +-- problem that std::vector and std::deque both handle badly. +-- +-- HEADER-ONLY, and unusually literally so: the entire library is ONE file, +-- `plf_hive.h`, sitting at the tarball root beside the license and the test +-- suite. So `include_dirs = { "*" }` (the `*` absorbs the archive's +-- `plf_hive-/` wrap layer) is the whole story, and consumers write +-- `#include `. A trivial anchor TU gives mcpp a buildable `lib` +-- target, the same shape compat.eigen / compat.opengl / compat.khrplatform use. +-- +-- VERSIONING: upstream cuts no tags and publishes no releases — development is +-- a linear series of commits on master, and the README tracks a version number +-- that does not appear in the repository as a ref. Following the precedent set +-- by compat.khrplatform (which mirrors the untagged EGL-Registry), this pins a +-- commit archive under a DATE version. `2026.07.31` is the commit date of +-- 085899f5, so the version key sorts correctly against any future snapshot. +-- +-- `plf_hive_test_suite.cpp` is upstream's own test driver, not part of the +-- library; it is not compiled here and the include path does not hide it (a +-- consumer that wants it can name it explicitly). +package = { + spec = "1", + namespace = "compat", + name = "plf-hive", + description = "plf::hive — reference implementation of the proposed std::hive (header-only)", + licenses = {"Zlib"}, + repo = "https://github.com/mattreecebentley/plf_hive", + type = "package", + + xpm = { + linux = { + ["2026.07.31"] = { + url = { + GLOBAL = "https://github.com/mattreecebentley/plf_hive/archive/085899f55591e77d49ed168be4594200aa0f0c3a.tar.gz", + CN = "https://gitcode.com/mcpp-res/plf-hive/releases/download/2026.07.31/plf-hive-2026.07.31.tar.gz", + }, + sha256 = "507555191c27768dc469cf7e435ceca0f2819a241f141b3d36a61c6396e500c4", + }, + }, + macosx = { + ["2026.07.31"] = { + url = { + GLOBAL = "https://github.com/mattreecebentley/plf_hive/archive/085899f55591e77d49ed168be4594200aa0f0c3a.tar.gz", + CN = "https://gitcode.com/mcpp-res/plf-hive/releases/download/2026.07.31/plf-hive-2026.07.31.tar.gz", + }, + sha256 = "507555191c27768dc469cf7e435ceca0f2819a241f141b3d36a61c6396e500c4", + }, + }, + windows = { + ["2026.07.31"] = { + url = { + GLOBAL = "https://github.com/mattreecebentley/plf_hive/archive/085899f55591e77d49ed168be4594200aa0f0c3a.tar.gz", + CN = "https://gitcode.com/mcpp-res/plf-hive/releases/download/2026.07.31/plf-hive-2026.07.31.tar.gz", + }, + sha256 = "507555191c27768dc469cf7e435ceca0f2819a241f141b3d36a61c6396e500c4", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + -- Tarball root: `#include `. + include_dirs = { "*" }, + generated_files = { + ["mcpp_generated/plf_hive_anchor.c"] = [==[ +int mcpp_compat_plf_hive_headers_anchor(void) { return 0; } +]==], + }, + sources = { "mcpp_generated/plf_hive_anchor.c" }, + targets = { ["plf-hive"] = { kind = "lib" } }, + deps = { }, + }, +} diff --git a/pkgs/c/compat.spirv-reflect.lua b/pkgs/c/compat.spirv-reflect.lua new file mode 100644 index 00000000..bdf1cfff --- /dev/null +++ b/pkgs/c/compat.spirv-reflect.lua @@ -0,0 +1,83 @@ +-- Form B inline descriptor for SPIRV-Reflect — Khronos' official reflection +-- library for SPIR-V modules. Given a compiled shader blob it enumerates +-- descriptor bindings, sets, push-constant blocks, and interface variables, which +-- is how a renderer builds pipeline layouts from shaders instead of duplicating +-- the binding table by hand. +-- +-- SHAPE: one C translation unit. `spirv_reflect.c` at the tarball root is the +-- entire library (upstream's CMake `spirv-reflect-static` target compiles exactly +-- this file); everything else in the tree is the `spirv-reflect` CLI, tests, and +-- the GoogleTest submodule, none of which belong in a consumable package. +-- +-- INCLUDE PATHS -- both are needed, for different reasons: +-- +-- `*` so consumers write `#include `, the name +-- upstream's README and every downstream project use. +-- `*/include` so the SPIRV_REFLECT_USE_SYSTEM_SPIRV_H path resolves. +-- spirv_reflect.h:35-37 picks between `` +-- (that macro defined) and `"./include/spirv/unified1/spirv.h"` +-- (the default, relative to the header, which works with `*` +-- alone). Exposing `include/` too means BOTH spellings resolve to +-- the SAME bundled grammar header, so a consumer that defines the +-- macro -- e.g. because something else in its graph already +-- supplies the Khronos headers -- does not silently get a +-- different SPIR-V revision than the one this .c was written for. +-- +-- VERSIONING: upstream cuts no semver releases; it tags in lockstep with the +-- Vulkan SDK (`vulkan-sdk-`). The version key drops the `vulkan-sdk-` +-- prefix so it sorts numerically and lines up with compat.vulkan-headers / +-- compat.vulkan of the same SDK -- keep the three moving together. +-- +-- No deps: the library is freestanding C99 over the bundled SPIR-V grammar and +-- does not need the Vulkan headers or the loader (it parses a SPIR-V blob, it +-- does not call Vulkan). +package = { + spec = "1", + namespace = "compat", + name = "spirv-reflect", + description = "Khronos SPIRV-Reflect — reflection of descriptor bindings and interfaces from SPIR-V modules", + licenses = {"Apache-2.0"}, + repo = "https://github.com/KhronosGroup/SPIRV-Reflect", + type = "package", + + xpm = { + linux = { + ["1.4.357.0"] = { + url = { + GLOBAL = "https://github.com/KhronosGroup/SPIRV-Reflect/archive/refs/tags/vulkan-sdk-1.4.357.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/spirv-reflect/releases/download/1.4.357.0/spirv-reflect-1.4.357.0.tar.gz", + }, + sha256 = "c865bd55459c5b8020a6ac962e462fc33eb4bf2dae8bf7c474357c58ce22a95d", + }, + }, + macosx = { + ["1.4.357.0"] = { + url = { + GLOBAL = "https://github.com/KhronosGroup/SPIRV-Reflect/archive/refs/tags/vulkan-sdk-1.4.357.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/spirv-reflect/releases/download/1.4.357.0/spirv-reflect-1.4.357.0.tar.gz", + }, + sha256 = "c865bd55459c5b8020a6ac962e462fc33eb4bf2dae8bf7c474357c58ce22a95d", + }, + }, + windows = { + ["1.4.357.0"] = { + url = { + GLOBAL = "https://github.com/KhronosGroup/SPIRV-Reflect/archive/refs/tags/vulkan-sdk-1.4.357.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/spirv-reflect/releases/download/1.4.357.0/spirv-reflect-1.4.357.0.tar.gz", + }, + sha256 = "c865bd55459c5b8020a6ac962e462fc33eb4bf2dae8bf7c474357c58ce22a95d", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*", "*/include" }, + -- Upstream's spirv-reflect-static target is exactly this one file. + sources = { "*/spirv_reflect.c" }, + targets = { ["spirv-reflect"] = { kind = "lib" } }, + deps = { }, + }, +} diff --git a/pkgs/c/compat.vulkan-memory-allocator.lua b/pkgs/c/compat.vulkan-memory-allocator.lua new file mode 100644 index 00000000..85eeacf6 --- /dev/null +++ b/pkgs/c/compat.vulkan-memory-allocator.lua @@ -0,0 +1,113 @@ +-- Form B inline descriptor for the Vulkan Memory Allocator (VMA) — AMD/GPUOpen's +-- allocator for Vulkan device memory. Vulkan hands you a small number of large +-- heaps and expects you to sub-allocate; VMA does that sub-allocation, picks the +-- memory type for a usage, and handles buffer/image suballocation, defragmentation +-- and budgeting. +-- +-- SHAPE: header-only WITH a generated implementation TU. `include/vk_mem_alloc.h` +-- carries declarations plus, behind VMA_IMPLEMENTATION, the implementation. +-- Upstream ships no implementation .c/.cpp of its own (its `VmaUsage.cpp` lives +-- under `src/` with the test app, not the library), so this package generates +-- one -- that is what makes it linkable rather than a header drop. +-- +-- CONSEQUENCE FOR CONSUMERS: do NOT also define VMA_IMPLEMENTATION. It is +-- already compiled into this package's lib, and defining it again duplicates +-- every symbol -- a LINK error, so it surfaces late. Just +-- `#include ` and link. +-- +-- IT IS C++, NOT C: despite the C-shaped API, the implementation is C++14 and +-- must be compiled as C++ (upstream says so, and the file is full of templates +-- and STL containers). Hence the generated TU is `.cpp`, not `.c`. +-- +-- DEPENDENCY: compat.vulkan-headers, because vk_mem_alloc.h includes +-- for the handle and enum types. Headers only -- and that is +-- a deliberate choice that the implementation TU has to be built to match: +-- +-- VMA_STATIC_VULKAN_FUNCTIONS defaults to 1, which makes the implementation +-- reference vkGetBufferMemoryRequirements2, vkBindBufferMemory2, +-- vkGetPhysicalDeviceProperties2 and friends DIRECTLY. Those symbols live in +-- the loader, so with a headers-only dependency the package would not link +-- (verified: eight undefined references out of vk_mem_alloc.h:13600+). +-- +-- Pulling compat.vulkan to satisfy them would be the wrong fix: it would make +-- every consumer of a memory allocator link a Vulkan loader whether or not it +-- loads Vulkan that way, and it would fight consumers that dispatch through +-- volk or their own device-level table. +-- +-- So the generated TU selects the DYNAMIC path instead. VMA then calls +-- nothing by name; it resolves every entry point through the pointers in +-- VmaVulkanFunctions. +-- +-- CONSEQUENCE FOR CONSUMERS: fill in `VmaAllocatorCreateInfo::pVulkanFunctions` +-- with at least `vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` -- VMA fetches +-- the rest itself from those two. This is the mode engines that use volk or a +-- custom loader already run in. (The virtual-allocator API needs none of this, +-- which is why it works with no Vulkan at all.) +-- +-- Pinned to the same SDK line as compat.vulkan-headers / compat.spirv-reflect +-- (1.4.357.0); VMA's own 3.4.0 release targets Vulkan 1.4. +package = { + spec = "1", + namespace = "compat", + name = "vulkan-memory-allocator", + description = "AMD Vulkan Memory Allocator — device memory sub-allocation for Vulkan", + licenses = {"MIT"}, + repo = "https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator", + type = "package", + + xpm = { + linux = { + ["3.4.0"] = { + url = { + GLOBAL = "https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator/archive/refs/tags/v3.4.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/vulkan-memory-allocator/releases/download/3.4.0/vulkan-memory-allocator-3.4.0.tar.gz", + }, + sha256 = "822aa850c6ce77346ae96a8a1d351d52e77e85929f35363849a0a4e638e0a2a1", + }, + }, + macosx = { + ["3.4.0"] = { + url = { + GLOBAL = "https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator/archive/refs/tags/v3.4.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/vulkan-memory-allocator/releases/download/3.4.0/vulkan-memory-allocator-3.4.0.tar.gz", + }, + sha256 = "822aa850c6ce77346ae96a8a1d351d52e77e85929f35363849a0a4e638e0a2a1", + }, + }, + windows = { + ["3.4.0"] = { + url = { + GLOBAL = "https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator/archive/refs/tags/v3.4.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/vulkan-memory-allocator/releases/download/3.4.0/vulkan-memory-allocator-3.4.0.tar.gz", + }, + sha256 = "822aa850c6ce77346ae96a8a1d351d52e77e85929f35363849a0a4e638e0a2a1", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*/include" }, + -- Upstream ships no implementation TU for the library; supply one. + -- C++, not C: the implementation is C++14 despite the C-shaped API. + generated_files = { + ["mcpp_generated/vma_impl.cpp"] = [==[ +// Resolve Vulkan through VmaVulkanFunctions rather than by symbol name, so +// this package links against the Vulkan HEADERS alone -- no loader. See the +// descriptor header for why, and for what the consumer must supply. +#define VMA_STATIC_VULKAN_FUNCTIONS 0 +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 + +#define VMA_IMPLEMENTATION +#include +]==], + }, + sources = { "mcpp_generated/vma_impl.cpp" }, + targets = { ["vulkan-memory-allocator"] = { kind = "lib" } }, + -- Headers only: VMA calls Vulkan through pointers the consumer supplies, + -- so the loader is the consumer's dependency, not ours. + deps = { ["compat.vulkan-headers"] = "1.4.357.0" }, + }, +} diff --git a/tests/examples/gtl/mcpp.toml b/tests/examples/gtl/mcpp.toml new file mode 100644 index 00000000..501cee6c --- /dev/null +++ b/tests/examples/gtl/mcpp.toml @@ -0,0 +1,8 @@ +# gtl test project: exercise the two container families people actually reach +# for -- the Swiss-table flat_hash_map and the ordered btree_set. +[package] +name = "gtl-tests" +version = "0.1.0" + +[dependencies.compat] +gtl = "1.2.0" diff --git a/tests/examples/gtl/tests/containers.cpp b/tests/examples/gtl/tests/containers.cpp new file mode 100644 index 00000000..cf7e3c15 --- /dev/null +++ b/tests/examples/gtl/tests/containers.cpp @@ -0,0 +1,54 @@ +// compat.gtl — exercise the two container families that are the reason to pull +// gtl in: the Swiss-table flat_hash_map and the ordered btree_set. Every check +// asserts observable behaviour (contents, ordering, erasure), not just that the +// headers compile. +#include +#include +import std; + +int main() { + bool ok = true; + auto check = [&](bool cond, std::string_view what) { + if (!cond) { + std::println("FAIL: {}", what); + ok = false; + } + }; + + // ---- flat_hash_map --------------------------------------------------- + gtl::flat_hash_map m; + for (int i = 0; i < 1000; ++i) m.emplace(std::format("k{}", i), i); + check(m.size() == 1000, "flat_hash_map size after 1000 emplaces"); + check(m.at("k0") == 0 && m.at("k999") == 999, "lookup returns what was stored"); + check(m.find("absent") == m.end(), "miss returns end()"); + + // Rehashing must not lose or corrupt entries. + long sum = 0; + for (const auto& [k, v] : m) sum += v; + check(sum == 999L * 1000 / 2, "every value survives rehashing"); + + check(m.erase("k500") == 1, "erase reports one removal"); + check(m.erase("k500") == 0, "second erase reports none"); + check(m.size() == 999, "size after erase"); + check(!m.contains("k500"), "erased key is gone"); + + // ---- parallel_flat_hash_map ----------------------------------------- + // Same API, internally sharded. Worth touching because it is gtl's + // headline container and instantiates a different code path. + gtl::parallel_flat_hash_map pm; + for (int i = 0; i < 500; ++i) pm[i] = i * i; + check(pm.size() == 500, "parallel map size"); + check(pm[20] == 400, "parallel map value"); + + // ---- btree_set ------------------------------------------------------- + // Unlike the hash maps, this one is ORDERED -- assert that. + gtl::btree_set s; + for (int i : {42, 7, 99, 1, 63, 7}) s.insert(i); + check(s.size() == 5, "btree_set drops the duplicate"); + check(std::ranges::is_sorted(s), "btree_set iterates in order"); + check(*s.begin() == 1 && *s.rbegin() == 99, "btree_set endpoints"); + check(*s.lower_bound(50) == 63, "btree_set lower_bound"); + + if (ok) std::println("gtl OK"); + return ok ? 0 : 1; +} diff --git a/tests/examples/miniaudio/mcpp.toml b/tests/examples/miniaudio/mcpp.toml new file mode 100644 index 00000000..a00c5122 --- /dev/null +++ b/tests/examples/miniaudio/mcpp.toml @@ -0,0 +1,9 @@ +# miniaudio test project: encode a WAV, decode it back, and assert the samples +# survive the round trip. Deliberately device-free -- CI has no sound card, so +# this exercises the codec/DSP paths rather than ma_device. +[package] +name = "miniaudio-tests" +version = "0.1.0" + +[dependencies.compat] +miniaudio = "0.11.25" diff --git a/tests/examples/miniaudio/tests/roundtrip.cpp b/tests/examples/miniaudio/tests/roundtrip.cpp new file mode 100644 index 00000000..eaced739 --- /dev/null +++ b/tests/examples/miniaudio/tests/roundtrip.cpp @@ -0,0 +1,90 @@ +// compat.miniaudio — encode a WAV, decode it back, and assert the samples +// survive the round trip. +// +// Deliberately DEVICE-FREE. A CI runner has no sound card, so ma_device_init +// would fail for reasons that say nothing about the package. The encoder, +// decoder and the WAV codec behind them are the parts that can be asserted +// anywhere, and they still prove the implementation TU (miniaudio.c) is +// compiled and linked -- none of these symbols exist without it. +#include +import std; + +int main() { + bool ok = true; + auto check = [&](bool cond, std::string_view what) { + if (!cond) { + std::println("FAIL: {}", what); + ok = false; + } + }; + + constexpr ma_uint32 RATE = 48000; + constexpr ma_uint32 CHANNELS = 1; + constexpr ma_uint64 FRAMES = 4800; // 100 ms + const std::string path = "miniaudio_roundtrip.wav"; + + // A 480 Hz sine: 10 whole cycles in 4800 frames, so the signal is + // continuous and its extremes are hit exactly. + std::vector written(FRAMES); + for (ma_uint64 i = 0; i < FRAMES; ++i) { + written[i] = 0.5f * std::sin(2.0f * std::numbers::pi_v * + 480.0f * static_cast(i) / RATE); + } + + // ---- encode ---------------------------------------------------------- + { + ma_encoder_config cfg = + ma_encoder_config_init(ma_encoding_format_wav, ma_format_f32, CHANNELS, RATE); + ma_encoder enc; + check(ma_encoder_init_file(path.c_str(), &cfg, &enc) == MA_SUCCESS, + "encoder opened the output file"); + + ma_uint64 put = 0; + check(ma_encoder_write_pcm_frames(&enc, written.data(), FRAMES, &put) == MA_SUCCESS, + "encoder accepted the frames"); + check(put == FRAMES, "encoder wrote every frame"); + ma_encoder_uninit(&enc); + } + + // ---- decode ---------------------------------------------------------- + { + ma_decoder_config cfg = ma_decoder_config_init(ma_format_f32, CHANNELS, RATE); + ma_decoder dec; + check(ma_decoder_init_file(path.c_str(), &cfg, &dec) == MA_SUCCESS, + "decoder opened the file just written"); + + check(dec.outputSampleRate == RATE, "decoded sample rate matches"); + check(dec.outputChannels == CHANNELS, "decoded channel count matches"); + + ma_uint64 total = 0; + check(ma_decoder_get_length_in_pcm_frames(&dec, &total) == MA_SUCCESS, + "decoder reported a length"); + check(total == FRAMES, "decoded length matches what was encoded"); + + std::vector read(FRAMES, 0.0f); + ma_uint64 got = 0; + check(ma_decoder_read_pcm_frames(&dec, read.data(), FRAMES, &got) == MA_SUCCESS, + "decoder returned frames"); + check(got == FRAMES, "decoder returned every frame"); + + // f32 WAV is lossless, so this is an equality check within float noise. + float worst = 0.0f; + for (ma_uint64 i = 0; i < got; ++i) { + worst = std::max(worst, std::abs(read[i] - written[i])); + } + check(worst < 1e-5f, "samples survive the round trip"); + + // Guard against the degenerate pass: silence would also round-trip. + float peak = 0.0f; + for (float v : read) peak = std::max(peak, std::abs(v)); + check(peak > 0.45f, "decoded signal is the sine, not silence"); + + ma_decoder_uninit(&dec); + } + + std::error_code ec; + std::filesystem::remove(path, ec); + + if (ok) std::println("miniaudio OK"); + return ok ? 0 : 1; +} diff --git a/tests/examples/nanosvg/mcpp.toml b/tests/examples/nanosvg/mcpp.toml new file mode 100644 index 00000000..3af2b6d7 --- /dev/null +++ b/tests/examples/nanosvg/mcpp.toml @@ -0,0 +1,9 @@ +# nanosvg test project: parse a real SVG document and rasterize it, asserting +# the geometry and the pixels -- which also proves the package's generated +# implementation TU is linked (both nanosvg and nanosvgrast). +[package] +name = "nanosvg-tests" +version = "0.1.0" + +[dependencies.compat] +nanosvg = "2026.07.09" diff --git a/tests/examples/nanosvg/tests/parse.cpp b/tests/examples/nanosvg/tests/parse.cpp new file mode 100644 index 00000000..97cdb4d6 --- /dev/null +++ b/tests/examples/nanosvg/tests/parse.cpp @@ -0,0 +1,85 @@ +// compat.nanosvg — parse a real SVG document and rasterize it. +// +// This asserts three separate things the package must get right: +// 1. `` / `` resolve (include_dirs points at src/), +// 2. the parser's output is real geometry, not an empty image, and +// 3. the generated implementation TU is actually linked -- BOTH halves of it. +// nsvgParse comes from nanosvg.h's implementation and nsvgRasterize from +// nanosvgrast.h's, so if the package only instantiated one of the two this +// test fails at link time rather than silently passing. +#include +#include +import std; + +int main() { + bool ok = true; + auto check = [&](bool cond, std::string_view what) { + if (!cond) { + std::println("FAIL: {}", what); + ok = false; + } + }; + + // A 100x100 canvas holding one opaque red rectangle covering the middle + // half. nsvgParse mutates its input, so hand it a writable copy. + std::string svg = + R"()" + R"()" + R"()"; + + NSVGimage* image = nsvgParse(svg.data(), "px", 96.0f); + check(image != nullptr, "nsvgParse returned an image"); + if (!image) return 1; + + check(image->width == 100.0f && image->height == 100.0f, "canvas dimensions"); + + int shapes = 0, paths = 0; + for (NSVGshape* sh = image->shapes; sh != nullptr; sh = sh->next) { + ++shapes; + for (NSVGpath* p = sh->paths; p != nullptr; p = p->next) ++paths; + } + check(shapes == 1, "one shape parsed"); + check(paths >= 1, "the shape has at least one path"); + + // The rect's bounds must be the ones the document declared. + if (image->shapes) { + const float* b = image->shapes->bounds; + check(std::abs(b[0] - 25.0f) < 0.5f && std::abs(b[1] - 25.0f) < 0.5f && + std::abs(b[2] - 75.0f) < 0.5f && std::abs(b[3] - 75.0f) < 0.5f, + "shape bounds match the declared rect"); + // fill colour is packed ABGR by nanosvg + check((image->shapes->fill.color & 0x00ffffffu) == 0x000000ffu, + "fill colour is red"); + } + + // ---- rasterize ------------------------------------------------------- + NSVGrasterizer* rast = nsvgCreateRasterizer(); + check(rast != nullptr, "rasterizer created"); + if (rast) { + constexpr int W = 100, H = 100; + std::vector px(static_cast(W) * H * 4, 0); + nsvgRasterize(rast, image, 0.0f, 0.0f, 1.0f, px.data(), W, H, W * 4); + + auto at = [&](int x, int y) -> const unsigned char* { + return px.data() + (static_cast(y) * W + x) * 4; + }; + // Inside the rect: opaque red. Outside: untouched (alpha 0). + const unsigned char* in = at(50, 50); + const unsigned char* out = at(5, 5); + check(in[3] > 200, "centre pixel is opaque"); + check(in[0] > 200 && in[1] < 60 && in[2] < 60, "centre pixel is red"); + check(out[3] == 0, "corner pixel is untouched"); + + std::size_t covered = 0; + for (std::size_t i = 3; i < px.size(); i += 4) if (px[i] > 128) ++covered; + // 50x50 of 100x100 = 2500 px, allow for antialiased edges. + check(covered > 2300 && covered < 2700, "covered area matches a 50x50 rect"); + + nsvgDeleteRasterizer(rast); + } + + nsvgDelete(image); + + if (ok) std::println("nanosvg OK"); + return ok ? 0 : 1; +} diff --git a/tests/examples/plf-hive/mcpp.toml b/tests/examples/plf-hive/mcpp.toml new file mode 100644 index 00000000..83ca2350 --- /dev/null +++ b/tests/examples/plf-hive/mcpp.toml @@ -0,0 +1,12 @@ +# plf-hive test project: assert the property that makes a hive worth using -- +# element addresses survive erasure of their neighbours -- rather than merely +# that the header parses. +# +# Qualified `compat.plf-hive` so the workspace-root [indices] redirect applies +# and this tests THIS checkout, not the published remote index. +[package] +name = "plf-hive-tests" +version = "0.1.0" + +[dependencies.compat] +plf-hive = "2026.07.31" diff --git a/tests/examples/plf-hive/tests/hive.cpp b/tests/examples/plf-hive/tests/hive.cpp new file mode 100644 index 00000000..77704d16 --- /dev/null +++ b/tests/examples/plf-hive/tests/hive.cpp @@ -0,0 +1,64 @@ +// compat.plf-hive — assert the guarantee that distinguishes a hive from a +// vector or a deque: an element's ADDRESS stays valid while its neighbours are +// erased and while new elements are inserted. If the package silently resolved +// to something else, or the header were stubbed, these checks fail rather than +// passing vacuously. +#include +import std; + +int main() { + bool ok = true; + auto check = [&](bool cond, std::string_view what) { + if (!cond) { + std::println("FAIL: {}", what); + ok = false; + } + }; + + plf::hive h; + + // Fill, and remember where a few elements physically live. + std::vector::iterator> its; + for (int i = 0; i < 200; ++i) its.push_back(h.insert(i)); + check(h.size() == 200, "size after 200 inserts"); + + const int* addr_of_50 = &*its[50]; + const int* addr_of_150 = &*its[150]; + + // Erase every even element. In a vector this would invalidate everything + // after the first erasure; in a hive nothing moves. + std::size_t erased = 0; + for (int i = 0; i < 200; i += 2) { + h.erase(its[static_cast(i)]); + ++erased; + } + check(erased == 100, "erased 100 elements"); + check(h.size() == 100, "size after erasures"); + check(&*its[51] != nullptr, "surviving iterator still dereferenceable"); + check(addr_of_50 != nullptr && addr_of_150 != nullptr, "addresses captured"); + + // Odd elements survived, at their original addresses. + const int* addr_of_51 = &*its[51]; + check(*its[51] == 51, "surviving element keeps its value"); + + // Inserting again reuses the erased slots -- and must not relocate anything. + for (int i = 0; i < 100; ++i) h.insert(1000 + i); + check(h.size() == 200, "size after refill"); + check(&*its[51] == addr_of_51, "address stable across erase + insert"); + check(*its[51] == 51, "value stable across erase + insert"); + + // The surviving originals are exactly the odd numbers. + long odd_sum = 0; + long expected = 0; + for (int v : h) if (v < 1000) odd_sum += v; + for (int i = 1; i < 200; i += 2) expected += i; + check(odd_sum == expected, "surviving elements are exactly the odd ones"); + + // Erasing through the range interface and clearing must leave it empty. + h.clear(); + check(h.empty(), "empty after clear"); + check(h.begin() == h.end(), "begin == end when empty"); + + if (ok) std::println("plf::hive OK"); + return ok ? 0 : 1; +} diff --git a/tests/examples/spirv-reflect/mcpp.toml b/tests/examples/spirv-reflect/mcpp.toml new file mode 100644 index 00000000..d18422f5 --- /dev/null +++ b/tests/examples/spirv-reflect/mcpp.toml @@ -0,0 +1,9 @@ +# spirv-reflect test project: reflect a REAL compute shader (compiled with +# glslc and embedded as a word array) and assert the descriptor bindings, +# sets and push-constant block come back with the values the shader declared. +[package] +name = "spirv-reflect-tests" +version = "0.1.0" + +[dependencies.compat] +spirv-reflect = "1.4.357.0" diff --git a/tests/examples/spirv-reflect/tests/reflect.cpp b/tests/examples/spirv-reflect/tests/reflect.cpp new file mode 100644 index 00000000..52cefb2c --- /dev/null +++ b/tests/examples/spirv-reflect/tests/reflect.cpp @@ -0,0 +1,155 @@ +// compat.spirv-reflect — reflect a REAL compute shader and assert the bindings +// come back with the values the shader declared. +// +// The module below is the SPIR-V that glslc emits for this GLSL: +// +// #version 450 +// layout(set = 0, binding = 3, std430) buffer Data { float values[]; } data; +// layout(set = 1, binding = 0) uniform Params { vec4 tint; } params; +// layout(push_constant) uniform Push { mat4 mvp; } push; +// layout(local_size_x = 64) in; +// void main() { data.values[gl_GlobalInvocationID.x] *= params.tint.x + push.mvp[0][0]; } +// +// It is embedded as words rather than compiled at test time so the test needs +// no shader compiler on the runner. Two sets, two different descriptor types, a +// non-zero binding number and a push-constant block mean a stub that returned +// zeroed structures could not pass. +#include +import std; + +namespace { +// glslc -fshader-stage=compute, SPIR-V 1.0 / Vulkan 1.0 +constexpr std::uint32_t kComputeSpv[] = { + 0x07230203, 0x00010000, 0x000d000b, 0x0000002b, 0x00000000, 0x00020011, + 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, + 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0006000f, 0x00000005, + 0x00000004, 0x6e69616d, 0x00000000, 0x00000010, 0x00060010, 0x00000004, + 0x00000011, 0x00000040, 0x00000001, 0x00000001, 0x00030003, 0x00000002, + 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70, + 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576, 0x00080004, + 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, + 0x00657669, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00040005, + 0x00000008, 0x61746144, 0x00000000, 0x00050006, 0x00000008, 0x00000000, + 0x756c6176, 0x00007365, 0x00040005, 0x0000000a, 0x61746164, 0x00000000, + 0x00080005, 0x00000010, 0x475f6c67, 0x61626f6c, 0x766e496c, 0x7461636f, + 0x496e6f69, 0x00000044, 0x00040005, 0x00000016, 0x61726150, 0x0000736d, + 0x00050006, 0x00000016, 0x00000000, 0x746e6974, 0x00000000, 0x00040005, + 0x00000018, 0x61726170, 0x0000736d, 0x00040005, 0x0000001d, 0x68737550, + 0x00000000, 0x00040006, 0x0000001d, 0x00000000, 0x0070766d, 0x00040005, + 0x0000001f, 0x68737570, 0x00000000, 0x00040047, 0x00000007, 0x00000006, + 0x00000004, 0x00050048, 0x00000008, 0x00000000, 0x00000023, 0x00000000, + 0x00030047, 0x00000008, 0x00000003, 0x00040047, 0x0000000a, 0x00000022, + 0x00000000, 0x00040047, 0x0000000a, 0x00000021, 0x00000003, 0x00040047, + 0x00000010, 0x0000000b, 0x0000001c, 0x00050048, 0x00000016, 0x00000000, + 0x00000023, 0x00000000, 0x00030047, 0x00000016, 0x00000002, 0x00040047, + 0x00000018, 0x00000022, 0x00000001, 0x00040047, 0x00000018, 0x00000021, + 0x00000000, 0x00040048, 0x0000001d, 0x00000000, 0x00000005, 0x00050048, + 0x0000001d, 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x0000001d, + 0x00000000, 0x00000007, 0x00000010, 0x00030047, 0x0000001d, 0x00000002, + 0x00040047, 0x0000002a, 0x0000000b, 0x00000019, 0x00020013, 0x00000002, + 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, + 0x0003001d, 0x00000007, 0x00000006, 0x0003001e, 0x00000008, 0x00000007, + 0x00040020, 0x00000009, 0x00000002, 0x00000008, 0x0004003b, 0x00000009, + 0x0000000a, 0x00000002, 0x00040015, 0x0000000b, 0x00000020, 0x00000001, + 0x0004002b, 0x0000000b, 0x0000000c, 0x00000000, 0x00040015, 0x0000000d, + 0x00000020, 0x00000000, 0x00040017, 0x0000000e, 0x0000000d, 0x00000003, + 0x00040020, 0x0000000f, 0x00000001, 0x0000000e, 0x0004003b, 0x0000000f, + 0x00000010, 0x00000001, 0x0004002b, 0x0000000d, 0x00000011, 0x00000000, + 0x00040020, 0x00000012, 0x00000001, 0x0000000d, 0x00040017, 0x00000015, + 0x00000006, 0x00000004, 0x0003001e, 0x00000016, 0x00000015, 0x00040020, + 0x00000017, 0x00000002, 0x00000016, 0x0004003b, 0x00000017, 0x00000018, + 0x00000002, 0x00040020, 0x00000019, 0x00000002, 0x00000006, 0x00040018, + 0x0000001c, 0x00000015, 0x00000004, 0x0003001e, 0x0000001d, 0x0000001c, + 0x00040020, 0x0000001e, 0x00000009, 0x0000001d, 0x0004003b, 0x0000001e, + 0x0000001f, 0x00000009, 0x00040020, 0x00000020, 0x00000009, 0x00000006, + 0x0004002b, 0x0000000d, 0x00000028, 0x00000040, 0x0004002b, 0x0000000d, + 0x00000029, 0x00000001, 0x0006002c, 0x0000000e, 0x0000002a, 0x00000028, + 0x00000029, 0x00000029, 0x00050036, 0x00000002, 0x00000004, 0x00000000, + 0x00000003, 0x000200f8, 0x00000005, 0x00050041, 0x00000012, 0x00000013, + 0x00000010, 0x00000011, 0x0004003d, 0x0000000d, 0x00000014, 0x00000013, + 0x00060041, 0x00000019, 0x0000001a, 0x00000018, 0x0000000c, 0x00000011, + 0x0004003d, 0x00000006, 0x0000001b, 0x0000001a, 0x00070041, 0x00000020, + 0x00000021, 0x0000001f, 0x0000000c, 0x0000000c, 0x00000011, 0x0004003d, + 0x00000006, 0x00000022, 0x00000021, 0x00050081, 0x00000006, 0x00000023, + 0x0000001b, 0x00000022, 0x00060041, 0x00000019, 0x00000024, 0x0000000a, + 0x0000000c, 0x00000014, 0x0004003d, 0x00000006, 0x00000025, 0x00000024, + 0x00050085, 0x00000006, 0x00000026, 0x00000025, 0x00000023, 0x00060041, + 0x00000019, 0x00000027, 0x0000000a, 0x0000000c, 0x00000014, 0x0003003e, + 0x00000027, 0x00000026, 0x000100fd, 0x00010038,}; +} // namespace + +int main() { + bool ok = true; + auto check = [&](bool cond, std::string_view what) { + if (!cond) { + std::println("FAIL: {}", what); + ok = false; + } + }; + + SpvReflectShaderModule mod{}; + const auto rc = spvReflectCreateShaderModule(sizeof(kComputeSpv), kComputeSpv, &mod); + check(rc == SPV_REFLECT_RESULT_SUCCESS, "module parsed"); + if (rc != SPV_REFLECT_RESULT_SUCCESS) return 1; + + check(mod.shader_stage == SPV_REFLECT_SHADER_STAGE_COMPUTE_BIT, "stage is compute"); + check(std::string_view{mod.entry_point_name} == "main", "entry point is main"); + + // ---- descriptor bindings -------------------------------------------- + std::uint32_t count = 0; + check(spvReflectEnumerateDescriptorBindings(&mod, &count, nullptr) == SPV_REFLECT_RESULT_SUCCESS, + "binding count query"); + check(count == 2, "two descriptor bindings"); + + std::vector bindings(count); + check(spvReflectEnumerateDescriptorBindings(&mod, &count, bindings.data()) == SPV_REFLECT_RESULT_SUCCESS, + "binding enumeration"); + + bool saw_storage = false, saw_uniform = false; + for (const auto* b : bindings) { + if (b->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) { + saw_storage = true; + check(b->set == 0, "storage buffer is in set 0"); + check(b->binding == 3, "storage buffer is at binding 3"); + } else if (b->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER) { + saw_uniform = true; + check(b->set == 1, "uniform buffer is in set 1"); + check(b->binding == 0, "uniform buffer is at binding 0"); + } + } + check(saw_storage, "the std430 buffer was reflected as a storage buffer"); + check(saw_uniform, "the uniform block was reflected as a uniform buffer"); + + // ---- descriptor sets ------------------------------------------------- + std::uint32_t sets = 0; + check(spvReflectEnumerateDescriptorSets(&mod, &sets, nullptr) == SPV_REFLECT_RESULT_SUCCESS, + "set count query"); + check(sets == 2, "two descriptor sets"); + + // ---- push constants -------------------------------------------------- + std::uint32_t blocks = 0; + check(spvReflectEnumeratePushConstantBlocks(&mod, &blocks, nullptr) == SPV_REFLECT_RESULT_SUCCESS, + "push constant count query"); + check(blocks == 1, "one push constant block"); + + std::vector pcs(blocks); + if (blocks == 1) { + check(spvReflectEnumeratePushConstantBlocks(&mod, &blocks, pcs.data()) == SPV_REFLECT_RESULT_SUCCESS, + "push constant enumeration"); + check(pcs[0]->size == 64, "the mat4 push constant block is 64 bytes"); + check(pcs[0]->offset == 0, "push constant block starts at offset 0"); + } + + spvReflectDestroyShaderModule(&mod); + + // ---- negative case --------------------------------------------------- + // Garbage must be REJECTED. Without this, a stub that always returns + // SUCCESS would pass everything above by accident. + const std::uint32_t junk[] = {0xdeadbeefu, 0u, 0u, 0u, 0u, 0u, 0u, 0u}; + SpvReflectShaderModule bad{}; + check(spvReflectCreateShaderModule(sizeof(junk), junk, &bad) != SPV_REFLECT_RESULT_SUCCESS, + "a bad magic number is rejected"); + + if (ok) std::println("SPIRV-Reflect OK"); + return ok ? 0 : 1; +} diff --git a/tests/examples/vulkan-memory-allocator/mcpp.toml b/tests/examples/vulkan-memory-allocator/mcpp.toml new file mode 100644 index 00000000..249f3879 --- /dev/null +++ b/tests/examples/vulkan-memory-allocator/mcpp.toml @@ -0,0 +1,10 @@ +# VMA test project: drive the VIRTUAL allocator, which is VMA's sub-allocation +# algorithm with the Vulkan calls removed. That makes it the one part of VMA +# that can be asserted for real on a CI machine with no GPU and no driver -- +# and it still proves the implementation TU is compiled and linked. +[package] +name = "vulkan-memory-allocator-tests" +version = "0.1.0" + +[dependencies.compat] +vulkan-memory-allocator = "3.4.0" diff --git a/tests/examples/vulkan-memory-allocator/tests/virtual_block.cpp b/tests/examples/vulkan-memory-allocator/tests/virtual_block.cpp new file mode 100644 index 00000000..8f8b1c5a --- /dev/null +++ b/tests/examples/vulkan-memory-allocator/tests/virtual_block.cpp @@ -0,0 +1,97 @@ +// compat.vulkan-memory-allocator — drive VMA's VIRTUAL allocator. +// +// Everything else in VMA needs a VkDevice, which a CI runner with no GPU and no +// driver cannot create. The virtual allocator is the same sub-allocation +// algorithm with the Vulkan calls stripped out: it hands out offsets inside a +// notional block. That makes it the one part of VMA whose BEHAVIOUR can be +// asserted here -- and since it is compiled from the same VMA_IMPLEMENTATION +// TU as the rest, it still proves the package's implementation is built and +// linked (these symbols do not exist in the header-only view). +#include +import std; + +int main() { + bool ok = true; + auto check = [&](bool cond, std::string_view what) { + if (!cond) { + std::println("FAIL: {}", what); + ok = false; + } + }; + + constexpr VkDeviceSize BLOCK = 1u << 20; // 1 MiB + + VmaVirtualBlockCreateInfo bci{}; + bci.size = BLOCK; + + VmaVirtualBlock block = VK_NULL_HANDLE; + check(vmaCreateVirtualBlock(&bci, &block) == VK_SUCCESS, "virtual block created"); + check(block != VK_NULL_HANDLE, "block handle is non-null"); + if (block == VK_NULL_HANDLE) return 1; + + check(vmaIsVirtualBlockEmpty(block), "a fresh block is empty"); + + // Allocate a set of differently-sized, differently-aligned chunks. + struct Chunk { VmaVirtualAllocation h; VkDeviceSize off, size; }; + std::vector chunks; + for (int i = 0; i < 16; ++i) { + VmaVirtualAllocationCreateInfo aci{}; + aci.size = static_cast(1024u << (i % 5)); + aci.alignment = 256; + + VmaVirtualAllocation h{}; + VkDeviceSize off = 0; + if (vmaVirtualAllocate(block, &aci, &h, &off) != VK_SUCCESS) { + check(false, "vmaVirtualAllocate succeeded"); + break; + } + check(off % 256 == 0, "returned offset honours the requested alignment"); + check(off + aci.size <= BLOCK, "allocation stays inside the block"); + chunks.push_back({h, off, aci.size}); + } + check(chunks.size() == 16, "all 16 allocations succeeded"); + check(!vmaIsVirtualBlockEmpty(block), "block is not empty while allocations live"); + + // The whole point of an allocator: no two live allocations may overlap. + std::ranges::sort(chunks, {}, &Chunk::off); + for (std::size_t i = 1; i < chunks.size(); ++i) { + check(chunks[i - 1].off + chunks[i - 1].size <= chunks[i].off, + "live allocations do not overlap"); + } + + // Statistics must account for what we asked for. + VkDeviceSize requested = 0; + for (const auto& c : chunks) requested += c.size; + + VmaStatistics stats{}; + vmaGetVirtualBlockStatistics(block, &stats); + check(stats.allocationCount == chunks.size(), "stats report every allocation"); + check(stats.allocationBytes == requested, "stats report the requested bytes"); + + // Freeing must return the space: after freeing everything the block is + // empty again and a single block-sized allocation fits. + for (const auto& c : chunks) vmaVirtualFree(block, c.h); + check(vmaIsVirtualBlockEmpty(block), "block is empty again after freeing all"); + + VmaVirtualAllocationCreateInfo whole{}; + whole.size = BLOCK; + VmaVirtualAllocation big{}; + VkDeviceSize big_off = 0; + check(vmaVirtualAllocate(block, &whole, &big, &big_off) == VK_SUCCESS, + "the entire block is allocatable once freed"); + check(big_off == 0, "the block-sized allocation starts at offset 0"); + vmaVirtualFree(block, big); + + // And an over-sized request must FAIL rather than silently succeed. + VmaVirtualAllocationCreateInfo toobig{}; + toobig.size = BLOCK * 2; + VmaVirtualAllocation none{}; + VkDeviceSize none_off = 0; + check(vmaVirtualAllocate(block, &toobig, &none, &none_off) != VK_SUCCESS, + "an over-sized request is rejected"); + + vmaDestroyVirtualBlock(block); + + if (ok) std::println("VulkanMemoryAllocator OK"); + return ok ? 0 : 1; +}