diff --git a/.agents/docs/2026-11-14-add-basis-universal-mwrap-plan.md b/.agents/docs/2026-11-14-add-basis-universal-mwrap-plan.md new file mode 100644 index 00000000..9c773964 --- /dev/null +++ b/.agents/docs/2026-11-14-add-basis-universal-mwrap-plan.md @@ -0,0 +1,41 @@ +# Add mcpplibs.mwrap.basis_universal (basis-universal-m 2.50) + +## Form + +- Third-party upstream [BinomialLLC/basis_universal](https://github.com/BinomialLLC/basis_universal), release v2_50, + Apache-2.0 (the Zstandard decoder it ships beside the transcoder is BSD-licensed). +- Form A external mwrap module repository [mcpplibs/basis-universal-m](https://github.com/mcpplibs/basis-universal-m), + package `mcpplibs.mwrap.basis_universal`, version 2.50, tag `mwrap-2.50`; release asset + `basis-universal-m-2.50.tar.gz` (`eb114f20…8921`), CN mirror `gitcode.com/mcpp-res/mwrap-basis-universal` with + identical bytes. +- Implementation ownership: the index has no KTX2 or Basis Universal package, so the wrapper vendors the transcoder + (`transcoder/`: one source file with its headers and tables) and the Zstandard decoder upstream ships beside it + (`zstd/zstddeclib.c`, `zstd.h`, `zstd_errors.h`) — 33 files, checksums in `upstream/documents.json` — and builds + them as upstream's default build does (every format, KTX2, Zstandard), with one choice: floating-point contraction + off. The encoder, the command line tool and the bindings are not vendored. It deploys upstream's README, LICENSE + and NOTICE and Zstandard's LICENSE with its own LICENSE/NOTICE (`runtime.deploy`). No dependencies. + +## Module + +- `mcpplibs.mwrap.basis_universal`: every name `` declares at namespace scope in + `basist`, `basisu` and the namespaces inside them — 148 types, 273 functions, 70 objects, 112 enumerators of + unscoped enumerations (603 names) — the upstream entities by `using`, in upstream's namespaces. The header brings + upstream's internal header with it, so the tables and helpers are there as they are for a program that includes it. +- Adaptation: a module cannot export a name of internal linkage. The header's 139 scalar constants that are `const` + and not `inline` are declared again, with their names, types and values, under + `mcpplibs::mwrap::basis_universal::`. 11 names cannot be given again (five `static` functions, a + constant table, five `const float`s that are no constant expressions) and are left to the header; a translation + unit may include it beside the import (`tests/header_mix_test.cpp`). +- The export list is derived by `tools/surface.py` from the header as clang reads it (its JSON AST) and audited in CI + (`tools/check_exports.py`; derived again on the Linux LLVM row). + +## Verification + +- Wrapper CI (Linux x86_64 GCC 16.1 and LLVM 23.1.3, macOS LLVM, Windows LLVM): upstream's own KTX2 test files — + what each is (size, levels, codec, alpha); an ETC1S photograph as RGBA pixels and as BC7, ETC2 and ASTC blocks, each + pinned by a hash that holds on every row; one XUASTC picture packed by Zstandard and by the arithmetic coder is one + picture; noise, a file cut at four places and a changed identifier refused; header and module in one translation + unit; the export audit; the default template from an installed copy with a strict build. +- This index: consumer `tests/mwrap/basis-universal-transcode` (the transcoder made ready, what it writes, block + sizes, refusals, the adapted constants; it carries no texture file); the installed-template check with `--deploy` + (`STATIC_NOTICES`). diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 46d891c9..dedeacc6 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1738,6 +1738,11 @@ jobs: shell: bash run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.libjpeg_turbo@3.2.1 --deploy + - name: Installed Basis Universal wrapper archive, actual default KTX2 template and its relocated notices + if: contains(format(' {0} ', env.MEMBERS), ' basis-universal-transcode ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.basis_universal@2.50 --deploy + - name: Installed libarchive wrapper archive, actual default archive template and its relocated notices if: contains(format(' {0} ', env.MEMBERS), ' libarchive-bundle ') && matrix.toolchain == 'default' shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 14ed8bc1..2b2d0a98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,16 @@ ### Added +- **mcpplibs.mwrap.basis_universal 2.50(新包)。** basis-universal-m:Basis Universal 2.50 转码器(把 KTX2 与 `.basis` 纹理转成 + 像素或 GPU 自己的压缩格式)的 C++23 模块,`` 在 `basist`、`basisu` 及其内部命名空间声明的 + 603 个名字(148 个类型、273 个函数、70 个对象、112 个无作用域枚举的枚举项)以原名导出;头文件里 139 个内部链接的标量常量 + 以原名位于 `mcpplibs::mwrap::basis_universal::<所在命名空间>`,11 个无法重新给出的内部链接名字(`static` 函数、常量表、 + 非常量表达式的 `const float`)留给头文件。封装仓库逐字节收录 v2_50 发布包的 `transcoder/` 与上游随附的 Zstandard 解码器 + (`zstd/zstddeclib.c`),按上游默认方式构建(全部格式、KTX2、Zstandard),关闭浮点收缩;不含编码器。封装的测试读取上游 + 自己的 KTX2 测试文件:ETC1S 照片转成 RGBA 像素与 BC7、ETC2、ASTC 块的哈希在四个平台行一致,同一幅 XUASTC 图的两种打包 + 给出同一幅图。消费者测试 `tests/mwrap/basis-universal-transcode`,安装后模板检查带 `--deploy`。GLOBAL 为 GitHub 发布资产, + CN 为 GitCode `mcpp-res/mwrap-basis-universal` 镜像,sha256 相同。 + - **mcpplibs.mwrap.libjpeg_turbo 3.2.1(新包)。** libjpeg-turbo-m:libjpeg-turbo 的 TurboJPEG API(JPEG 压缩、解压、无损变换)的 C++23 模块,`` 的 7 个枚举、68 个枚举值、4 个类型、80 个函数以原名导出;头文件里的 43 个常量宏、9 个 `static const` 常量与表、3 个带参数的宏(`tj3Init`、`TJSCALED`、`TJPAD`)以原名位于 `mcpplibs::mwrap::libjpeg_turbo`。 diff --git a/mcpp.toml b/mcpp.toml index d6d6a644..cd5bea10 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -38,6 +38,7 @@ members = [ "tests/mwrap/meshoptimizer-simplify", "tests/mwrap/cgltf-inspect", "tests/mwrap/manifold-boolean", + "tests/mwrap/basis-universal-transcode", "tests/mwrap/libjpeg-turbo-decode", "tests/mwrap/libarchive-bundle", "tests/mwrap/libc-stdio", diff --git a/pkgs/mwrap/basis_universal.lua b/pkgs/mwrap/basis_universal.lua new file mode 100644 index 00000000..7f5c6e41 --- /dev/null +++ b/pkgs/mwrap/basis_universal.lua @@ -0,0 +1,52 @@ +-- Immutable upstream-aligned mwrap source release (basis-universal-m mwrap-2.50); +-- GLOBAL and CN bytes are identical. +-- +-- The wrapper is Apache-2.0; the library it vendors and links statically, Basis +-- Universal 2.50's transcoder, is Apache-2.0 (Binomial LLC), and the Zstandard +-- decoder upstream ships beside it is BSD-licensed (Facebook, Inc.): the package +-- deploys upstream's README, LICENSE and NOTICE and Zstandard's LICENSE beside +-- programs (runtime.deploy). It has no dependencies. +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "basis_universal", + description = "basis-universal-m: Basis Universal 2.50's transcoder (KTX2 and .basis textures to pixels or to a GPU's own compressed format) as a C++23 module", + licenses = { "Apache-2.0" }, + repo = "https://github.com/mcpplibs/basis-universal-m", + type = "package", + extensions = { + autoupdate = { + tag = "mwrap-${version}", + url = "https://github.com/mcpplibs/basis-universal-m/releases/download/${tag}/basis-universal-m-${version}.tar.gz", + }, + }, + xpm = { + linux = { + ["2.50"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/basis-universal-m/releases/download/mwrap-2.50/basis-universal-m-2.50.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-basis-universal/releases/download/2.50/basis-universal-m-2.50.tar.gz", + }, + sha256 = "eb114f2026bb408fd73cc77852fc7539441d2717bac36bdc10bf7f4d09228921", + }, + }, + macosx = { + ["2.50"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/basis-universal-m/releases/download/mwrap-2.50/basis-universal-m-2.50.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-basis-universal/releases/download/2.50/basis-universal-m-2.50.tar.gz", + }, + sha256 = "eb114f2026bb408fd73cc77852fc7539441d2717bac36bdc10bf7f4d09228921", + }, + }, + windows = { + ["2.50"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/basis-universal-m/releases/download/mwrap-2.50/basis-universal-m-2.50.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-basis-universal/releases/download/2.50/basis-universal-m-2.50.tar.gz", + }, + sha256 = "eb114f2026bb408fd73cc77852fc7539441d2717bac36bdc10bf7f4d09228921", + }, + }, + }, +} diff --git a/tests/mwrap/basis-universal-transcode/mcpp.toml b/tests/mwrap/basis-universal-transcode/mcpp.toml new file mode 100644 index 00000000..48c7ccb7 --- /dev/null +++ b/tests/mwrap/basis-universal-transcode/mcpp.toml @@ -0,0 +1,11 @@ +[package] +name = "basis-universal-transcode" +version = "2.50" +description = "Consume the immutable Basis Universal wrapper: the transcoder made ready, what it can write and how large a block is, what is no KTX2 file refused, the adapted constants" + +[indices] +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. + +[dependencies] +mcpplibs.mwrap.basis_universal = "2.50" diff --git a/tests/mwrap/basis-universal-transcode/tests/behavior.cpp b/tests/mwrap/basis-universal-transcode/tests/behavior.cpp new file mode 100644 index 00000000..4a051dab --- /dev/null +++ b/tests/mwrap/basis-universal-transcode/tests/behavior.cpp @@ -0,0 +1,35 @@ +// The installed Basis Universal wrapper: the transcoder made ready, what this +// build of it can write, how large a block of each format is, what is no KTX2 +// file refused at each step, and the header's constants of internal linkage +// under their adapted names. (Pictures themselves are read in the wrapper's +// own CI, from upstream's test files: this consumer carries no file.) +import std; +import mcpplibs.mwrap.basis_universal; + +namespace adapted = mcpplibs::mwrap::basis_universal; + +int main() { + static_assert(adapted::basist::KTX2_KHR_DF_TRANSFER_LINEAR == 1 && adapted::basist::KTX2_KHR_DF_TRANSFER_SRGB == 2); + basist::basisu_transcoder_init(); + if (!basist::basisu_transcoder_supports_ktx2() || !basist::basisu_transcoder_supports_ktx2_zstd()) return 1; + using format = basist::transcoder_texture_format; + if (basist::basis_get_bytes_per_block_or_pixel(format::cTFRGBA32) != 4 || basist::basis_get_bytes_per_block_or_pixel(format::cTFBC7_RGBA) != 16 || + basist::basis_get_bytes_per_block_or_pixel(format::cTFETC1_RGB) != 8) + return 2; + if (!basist::basis_transcoder_format_is_uncompressed(format::cTFRGBA32) || basist::basis_transcoder_format_is_uncompressed(format::cTFASTC_4x4_RGBA)) return 3; + if (std::string_view(basist::basis_get_format_name(format::cTFBC7_RGBA)) != "BC7_RGBA") return 4; + if (!basist::basis_is_format_supported(format::cTFBC7_RGBA, basist::basis_tex_format::cETC1S) || !basist::basis_is_format_supported(format::cTFRGBA32, basist::basis_tex_format::cUASTC_LDR_4x4)) + return 5; + + // Noise, and the twelve bytes of KTX2's identifier with nothing after them: refused, and nothing read past the end. + basist::ktx2_transcoder transcoder; + const std::vector noise(4096, 0x5a); + if (transcoder.init(noise.data(), static_cast(noise.size()))) return 6; + const std::array identifier{0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A}; + if (transcoder.init(identifier.data(), static_cast(identifier.size()))) return 7; + if (transcoder.start_transcoding()) return 8; + std::array out{}; + if (transcoder.transcode_image_level(0, 0, 0, out.data(), 16, format::cTFRGBA32)) return 9; + std::println("Basis Universal transcoder: KTX2 with Zstandard; {} is {} bytes a block", basist::basis_get_format_name(format::cTFBC7_RGBA), basist::basis_get_bytes_per_block_or_pixel(format::cTFBC7_RGBA)); + return 0; +} diff --git a/tests/mwrap/check_installed_template.py b/tests/mwrap/check_installed_template.py index f3e22fcd..604f79a1 100644 --- a/tests/mwrap/check_installed_template.py +++ b/tests/mwrap/check_installed_template.py @@ -24,6 +24,7 @@ "mcpplibs.mwrap.cgltf": ("cgltf.cppm", "upstream/cgltf/LICENSE", b"Permission is hereby granted", b"Kuhlmann"), "mcpplibs.mwrap.manifold": ("manifold.cppm", "upstream/manifold/LICENSE", b"Apache License", b"The Manifold Authors"), "mcpplibs.mwrap.libjpeg_turbo": ("libjpeg_turbo.cppm", "upstream/libjpeg-turbo/README.ijg", b"Independent JPEG Group", b"libjpeg-turbo Project"), + "mcpplibs.mwrap.basis_universal": ("basis_universal.cppm", "upstream/basis_universal/NOTICE", b"Binomial LLC", b"Basis Universal"), "mcpplibs.mwrap.libc": ("libc.cppm", None, b"", b"ISO/IEC 9899:2018"), }