Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
18ad9d9
examples/10-graphics: 可移植性驱动默认是看不见的
speak-agent Sep 7, 2026
ebcbf78
build.mcpp: three channels a rule package had no way to reach the eng…
speak-agent Sep 7, 2026
235449a
tests/e2e/188: the "did not rerun" assertion could not fail
speak-agent Sep 7, 2026
1a3fa43
examples/10-graphics: the offscreen example runs on all three platforms
speak-agent Sep 7, 2026
90be6a9
examples/10-graphics: Windows stays at "builds", and the reason is a …
speak-agent Sep 7, 2026
c0225bb
A rule package declares what it compiles, so a new device language co…
speak-agent Sep 7, 2026
f7f7358
docs: the two keys, the table that now has two sources, and what was …
speak-agent Sep 7, 2026
15ce353
docs/20: why one lane's interface is generated and the other's is wri…
speak-agent Sep 7, 2026
b44b535
docs/20: whether the island header is required, and what omitting it …
speak-agent Sep 7, 2026
102cad6
docs/20: noun-phrase headings, which the style check requires
speak-agent Sep 7, 2026
8b931ec
build.mcpp: a synthesis that cannot write its program refuses
speak-agent Sep 7, 2026
77d4918
ci: correct why the Windows graphics run reaches no device
speak-agent Sep 7, 2026
4e2b476
build.mcpp: a build program is told which package it is building
speak-agent Sep 7, 2026
c17f736
docs/07: the two new rows state the fallback without addressing the r…
speak-agent Sep 7, 2026
553ce9c
CHANGELOG: the entry describes what shipped, not what was tried first
speak-agent Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,11 @@ install/config 形状,而那套形状是按 Linux 写的:
- **二期(shaderc 三平台)**:xim-pkgindex #778,规则按平台选编译器。已实现。
- **三期(CUDA/SYCL 上 Windows)**:xim-pkgindex #779(五个包),规则侧的路径推导与
路线判断。已实现;端到端由 `windows-test` job 装卸五个包并断言注册的程序验证。
- **四期(软件设备把 CI 上限抬到运行)**:**未实现,记录为发布工作**。Windows 上需要
- **四期(软件设备把 CI 上限抬到运行)**:**部分实现**。程序侧那一半做完了:一个
Vulkan 程序在 macOS 上**看不见任何设备**,除非它显式打开可移植性枚举 —— MoltenVK 是
portability driver,而 loader 默认不把这种驱动交给 `vkEnumeratePhysicalDevices`。
示例现在按**能力**而不是按 `#ifdef __APPLE__` 问这件事(见 10.4e)。载荷侧仍未做,
记录为发布工作。Windows 上需要
一个 Mesa-on-Windows 的 `vulkan_lvp` 构建;macOS 上 runner 自带 GPU 与 MoltenVK,
所以那一侧不需要软件设备,需要的是把 `compat.vulkan` 的 macOS 腿接到示例上。
今天的上限:三平台**构建**,Linux **运行**。
Expand Down Expand Up @@ -396,6 +400,31 @@ include 它。**
同一版还给「每条规则都为本宿主编译过」那个夹具补了它自己的分母:它断言的是「每一条」,
而「每一条」是它 carry 的一张清单。

### 10.4e 四期的程序侧:可移植性驱动默认是看不见的

把软件/可移植设备接到 CI 上之前,先测了上游产物:

| 产物 | 读数 |
|---|---|
| `MoltenVK-macos.tar` v1.4.2(Khronos 官方) | 92 个条目;`dynamic/dylib/macOS/libMoltenVK.dylib` 是 x86_64+arm64 的 universal binary(11 MB),ICD 用相对路径 `./libMoltenVK.dylib`,并带 **`"is_portability_driver": true`** |
| `mesa3d-26.2.0-release-msvc.7z`(pal1000 重分发) | `x64/vulkan_lvp.dll` 56 MB + `x64/lvp_icd.x86_64.json`;**只导入系统 DLL**(KERNEL32/GDI32/USER32/ADVAPI32/ole32/SHELL32/ntdll),没有 MSVCP140 —— 静态 CRT,机器上不需要装任何东西 |

第一行那个 `is_portability_driver` 是本节存在的理由:**loader 默认不把可移植性驱动交给
`vkEnumeratePhysicalDevices`**。一个照着原生驱动写的程序因此在 macOS 上一个设备都找不到,
并把它报成「这台机器没有 GPU」—— 诊断是错的,而它看起来完全合理。

规范要求的是成对的两半:实例要**启用** `VK_KHR_portability_enumeration` **并且**置位
`VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR`;随后凡是声明了
`VK_KHR_portability_subset` 的设备,必须在 `vkCreateDevice` 时启用它,否则调用失败。

示例按**能力**问这两件事,不按 `#ifdef __APPLE__`。性质是「我面前这个 loader 在展示
可移植性驱动」,而 `#ifdef` 两个方向都会错:Linux 上跑翻译层的机器也有它,而 macOS 上
对着原生驱动构建的程序并不需要它。实测:Linux/lavapipe 上读数不变,中心像素仍是
`(124, 70, 62, 255)`。

载荷侧(把 MoltenVK 与 Windows 的 lavapipe 收进索引)未做,因为那要往发布组织推新的
二进制资产,而 GitCode 的资产不可替换不可删除 —— 这是一个该由人做的决定,不该顺手做掉。

### 10.5 一条留下的不一致,以及它什么时候消失

`mcpp:plugins` 0.2.5/0.2.6 里 `xim:shaderc` 在 macOS 与 Windows 上是**精确版本**,而
Expand Down
860 changes: 860 additions & 0 deletions .agents/docs/2026-09-07-module-first-heterogeneous-surface.md

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions .github/workflows/ci-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,54 @@ jobs:
done
echo "ok: both shader stages compiled and the Vulkan half linked"

# AND NOW IT RUNS, WHICH IS A DIFFERENT CLAIM FROM THE ONE ABOVE.
#
# A build asserts that the shader compiler this platform publishes works
# and that the Vulkan half links. It cannot assert that the loader hands
# this program a device, and on macOS that is the interesting half:
# MoltenVK is a PORTABILITY driver, which the loader does not give to
# `vkEnumeratePhysicalDevices` unless the instance asked for portability
# enumeration. A program written against native drivers therefore finds no
# device here and reports it as "this machine has no GPU".
#
# This step is what turns that into a red build rather than a plausible
# message. It depends on the portability enumeration in this PR: without
# it the run reaches zero devices even though everything installed
# correctly.
#
# `VK_DRIVER_FILES` rather than an ICD search directory: the package is in
# the xlings store, not in `/usr/local/share/vulkan/icd.d`, and naming the
# file is the one form that does not depend on where the loader looks.
- name: "Graphics: the offscreen example RUNS on MoltenVK"
shell: bash
run: |
set -e
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
# NOT installed from here. The example declares `xim:moltenvk` under
# `cfg(macos)`, so the build above already provisioned it, and looking
# for the ICD without installing anything is what asserts that the
# declaration works. An `xlings install` here would make this step
# pass whether the manifest named the driver or not.
icd=$(find "${MCPP_HOME:-$HOME/.mcpp}/registry/data/xpkgs/xim-x-moltenvk" \
"$HOME/.xlings/data/xpkgs/xim-x-moltenvk" \
-name 'MoltenVK_icd.json' -print -quit 2>/dev/null || true)
[ -n "$icd" ] || { echo "no MoltenVK ICD in either store"; exit 1; }
echo "ICD: $icd"
cd "$GITHUB_WORKSPACE/examples/10-graphics/offscreen"
out=$(VK_DRIVER_FILES="$icd" "/tmp/mcpp-fresh" run 2>&1) || { echo "$out"; exit 1; }
echo "$out"
# The program asserts the corners and the centre itself and exits
# non-zero on either. What CI adds is that the run reached a DEVICE:
# a portability driver that the loader declined to show would leave
# the program reporting no device, which is the failure this step
# exists for. The name is not compared to a fixed string because it is
# the host's GPU and differs by runner; that it is non-empty and the
# centre pixel is opaque is what distinguishes reaching a device from
# not.
echo "$out" | grep -qE 'centre pixel: \([0-9]+, [0-9]+, [0-9]+, 255\)' \
|| { echo "no centre pixel was reported: the run reached no device"; exit 1; }
echo "ok: MoltenVK enumerated and the image was rendered on it"

# Integration: the mcpp built from THIS PR's source (the self-host binary,
# $MCPP = /tmp/mcpp-fresh) builds & runs a real external C++ project —
# xlings (openxlings/xlings ships its own mcpp.toml).
Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/ci-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,42 @@ jobs:
done
echo "ok: both shader stages compiled and the Vulkan half linked"

# WINDOWS STAYS AT "BUILDS", AND WHAT REMAINS OPEN IS THE DRIVER.
#
# A run step was written, pushed, and measured (run 34135108981). The
# manifest declaration worked -- `Provisioning [xlings.workspace] entries
# (xim:mesa-lavapipe@26.2.0)` -- the ICD manifest was found in the store,
# and the program printed `render unavailable`, which is `src/main.cpp`
# reporting that the render function returned nothing.
#
# THAT OUTPUT PLACES THE FAILURE AFTER THE LOADER, NOT AT IT. The Vulkan
# leg imports `vkCreateInstance` from `vulkan-1.dll` through the import
# library, so a process missing that DLL fails during image load and
# prints nothing at all. It printed. The loader was there, it ran, and it
# enumerated no device -- which is a statement about the ICD.
#
# An earlier revision of this comment named a missing `vulkan-1.dll` as
# the cause. That was an inference from the descriptor's note about static
# linkage rather than a reading of the failure, and the log refutes it.
# Two facts stand against it: this program ran, and mcpp-index's own
# `vulkan-tests` member calls `vkEnumerateInstanceVersion` on the windows
# shards and passes.
#
# A separate measurement, recorded here because it removes a second thing
# from the list of suspects: the Khronos loader in `compat:vulkan` builds
# into a working `vulkan-1.dll` from the source the index already carries
# (mingw, 265 exports matching upstream's `vulkan-1.def` exactly, DllMain
# present, importing only ADVAPI32/CFGMGR32/KERNEL32/msvcrt). The
# descriptor's note argues that a Windows loader must be a DLL, not that
# it cannot be built -- so a hermetic Windows loader is available whenever
# it is wanted. It is not what this step is waiting on.
#
# What this step is waiting on is `xim:mesa-lavapipe`'s Windows payload
# producing a device under an mcpp-launched process. Until that is
# measured, the job builds the Vulkan half and runs the CPU fallback.
# macOS is not in the same position: MoltenVK enumerates once the instance
# asks for portability, which `src/vulkan/render.cpp` now does.

- name: "Toolchain: LLVM — build mcpp (self-host)"
shell: bash
run: |
Expand Down
134 changes: 134 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,66 @@

## [2026.9.7.1] - 2026-09-07

### 四条通道,都是「规则包知道而引擎收不到」的形状

这一版加的四样东西各自很小,共同点是它们补的都是同一类缺口:一方已经知道某件事,
而没有任何通道把它送到需要它的另一方。

**`mcpp::action` 增加 `depfile`。** action 的输入在 `build.mcpp` 运行时就定死了,那时
命令还没执行。一个靠解析源码才知道自己 `#include` 图的编译器,因此没有办法把结果报回
来:改动一个命令只是**读**过的文件不会触发任何重建,而 `mcpp build` 会在一个陈旧产物上
保持绿色。声明了 depfile 之后,mcpp 为那条边写出 `depfile =` 与 `deps = gcc`,ninja
读取并把它列出的文件并入这条边的依赖。相关的每个设备编译器本来就能输出它 ——
`glslangValidator --depfile`、`glslc -MD -MF`、`slangc -depfile`、`nvcc`/`clang` 的
`-MD -MF` —— 缺的一直是接收端。

**不要同时把 depfile 声明为 `output()`**:`deps = gcc` 会让 ninja 读完即删,一条承诺了
该输出的边会永远是脏的。

**规则包自己声明它编译哪些扩展名(`device_extensions`),以及提供规则的模块
(`rule_module`)。** 受限 glob 的 `accel` 键**不**决定一个文件是不是设备源,
`kDeviceExtensions` 才决定。所以在此之前,一个规则包无法自己引入一门设备语言:
`xim:slang` 装得上、规则也编得过,而文件仍然掉进普通源集,报的是「mcpp has no role for
the extension '.slang'」。

第一版的修法是把 `.slang` 加进引擎那张表。那是**方向反了的依赖**:通用构建系统会因此
持有一份外部插件的语言清单,而第三方插件想引入一门语言仍要等一次引擎发布。最终的形状
是两个键写在规则包**自己**的 feature 上:

[features.rules-slang]
sources = ["rules/slang.cppm"]
device_extensions = [".slang"]
rule_module = "mcpp.rules.slang"

引擎因此不持有任何包名、feature 拼写或模块名。两个键必须同时出现:一个声明了扩展名却
不说谁来编译它的 feature 会被拒绝,而不是让文件在后面某处静默掉队。

判据是直接的:`.slang` 已从 `kDeviceExtensions` **移除**,而 `tests/slang-consumer`
照常构建与运行。一门新设备语言不再需要引擎发一个版本。

**`[rules]` 声明了规则的项目不必写 `build.mcpp`。** 规则模块被声明之后,那个程序的内容
就是确定的 —— 导入这些模块、依次调用 `compile()`、有一条失败就返回非零。mcpp 把它写进
构建目录。项目要接管就把它拷到根目录改,合成随即停止:声明是 `build.mcpp` **之上**的
一层,不是它旁边的第二套机制。

**`[language] modules` 以 `MCPP_LANGUAGE_MODULES` 报给构建程序。** **生成**面向消费者
声明的规则要在「模块接口」与「头文件」之间做选择,而项目已经声明过它用哪一种;用别的
方式推导就是同一个决定的第二种拼法。旧引擎不设这个变量,规则把缺席读作「头文件」——
也就是这个变量存在之前每个消费者的行为,所以升级不需要任何项目改一个字。

**包身份以 `MCPP_PKG_NAME` / `MCPP_PKG_NAMESPACE` 报给构建程序。** 规则包生成的每一个
名字都由包名推导 —— 消费者导入的模块、访问器所在的命名空间、生成头里的符号 —— 而构建
程序的契约里没有任何东西回答「我在构建哪个包」。此前可用的最接近的答案是
`MCPP_MANIFEST_DIR` 的末段,那是**目录名**。

两者在「包放在一个通用目录下」时不同,而这正是本仓库自己的布局:
`examples/09-heterogeneous/vulkan/app/` 声明 `name = "vulkan-saxpy"`,
`mcpp.rules.spirv` 生成的却是 `app.shaders` —— 工作区里每一个 `<something>/app/` 都会
声称拥有同一个模块,而且这个面向用户的名字会随着目录改名而改变。

配套的 e2e 按构造区分这两种推导:夹具的目录叫 `app`、包叫 `vulkan-saxpy`。名字与目录
相同的夹具对两种实现都通过 —— 而在此之前每一个夹具都是这样。

### 两处只在 Linux 之外成立的缺陷,以及第一条图形管线

**引擎与规则层早就与平台无关,而生态只在 Linux 上完整。** 把规则包在另外两个平台上
Expand Down Expand Up @@ -59,6 +119,80 @@ libmalloc 里 abort(#202)。CI 报的正是这条路的第一步:链接停在 `_
`cfg(accelerator = ...)` 下的 `[build]` 源生效而依赖被忽略,于是包被丢掉、包含它的源
被留下。

### 规则包自己声明它编译什么,于是新设备语言不再需要动引擎

`[features].<f>` 上的两个键:`device_extensions` 说这条规则编译哪些设备源扩展名,
`rule_module` 说消费者的构建程序 import 哪个模块去够到它。引擎里没有任何包名、feature
拼写或模块名。

**一门新设备语言不再触碰引擎。** 把 `.slang` 加进内置表曾经要一次引擎改动、一次发布,
以及规则包 CI 里的一次版本抬升,规则才能路由一个文件。这一版把 `.slang` 从那张表里
**移除**,改由 `rules-slang` 声明;`tests/slang-consumer` 原样构建并运行,这是判断这套
机制能不能承载一门语言的唯一诚实办法。内置表从此是「mcpp 不需要被告知就知道的那些」——
一个兼容集合,而不是第六个后端要加入的注册表。

**消费者写一条边,不写构建程序。** `rule_module` 蕴含 `host-module = true`,因为一个
点名了规则模块的 feature 已经说过那是使用它的唯一方式。而没有 `build.mcpp` 的包会拿到
那些规则描述的程序:

[build-dependencies.mcpp]
plugins = { version = "0.3.0", features = ["rules-spirv"] }

就是 `tests/spirv-zero-config` 的全部声明。自带程序的包保留自己的:合成只填补缺席。

**feature 仍然按名字请求。** 早先一版从工程源码里出现的扩展名推导这个集合,被撤销了:
两个包可能认领同一扩展名(第三方写一条处理 `.cu` 的规则是会发生的事),而且 manifest
的职责是描述这次构建,派生出来的集合让文件不再陈述它。

跑起来才发现的两处顺序缺陷:收集必须落在 feature 激活与「收窄受限 glob 的那张扩展名表」
之间,放在其后则声明来得太晚、规则拿到空清单;而构建程序调用点的守卫问的是「文件在不在」
而函数问的是「要不要一个程序」,于是合成不可达。两处现在问同一个问题。

### 可移植性驱动默认是看不见的,于是三个平台都从「构建」抬到「运行」

macOS 上没有原生 Vulkan,MoltenVK 是 Metal 之上的实现,规范把这种实现叫**可移植性驱动**。
loader 默认不把它交给 `vkEnumeratePhysicalDevices`:实例要同时启用
`VK_KHR_portability_enumeration` 并置位 `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR`,
随后凡是声明 `VK_KHR_portability_subset` 的设备必须在 `vkCreateDevice` 时启用它。一个照着
原生驱动写的程序因此在那台机器上**一个设备都找不到**,并把它报成「这台机器没有 GPU」——
诊断是错的,而它看起来完全合理。

按能力问,不按 `#ifdef __APPLE__`:性质是「我面前这个 loader 在展示可移植性驱动」,而
Linux 上跑翻译层的机器也有它,macOS 上对着原生驱动构建的程序并不需要它。Linux/lavapipe
上读数不变,中心像素仍是 `(124, 70, 62, 255)`,设备名仍是 `llvmpipe`。

**示例本身补齐了 macOS 的设备声明** —— `cfg(macos)` 下 `xim:moltenvk`。这个缺口在示例
只构建不运行时是看不见的:不运行的程序从不向 loader 要设备。声明写在示例里而不是 CI
步骤里,是为了让**任何人**检出它都能跑,而不只是那台多跑了一条命令的 runner。

于是新的 macOS CI 步骤**什么都不装**:它找构建已经供给的 ICD 并设 `VK_DRIVER_FILES`,
所以一份没能声明驱动的 manifest 会让步骤变红。步骤里放 `xlings install` 会让它两种情况
都通过。

Linux 与 macOS 断言的东西不同,而这个差别正是重点。lavapipe 是软件光栅化器,像素由构造
保证相同,所以图像分不出它,设备名才分得出。MoltenVK 是宿主自己的 GPU 经 Metal,名字随
runner 变,所以那边区分「够到了设备」的是**报出了中心像素**这件事本身 —— 一个被 loader
拒绝展示的可移植性驱动不会产生它。

**Windows 停在「构建」,而停在这里的理由被测量推翻过一次。** 运行步骤写过、推过、测过:
`xim:mesa-lavapipe` 装上了,ICD 清单在 store 里找到了,程序打印 `render unavailable` ——
那是 `src/main.cpp` 在渲染函数什么都没返回时的报告。

第一次读把它归因成缺少 loader。这个归因是错的,日志本身就说明了:Vulkan 那条腿经导入库
从 `vulkan-1.dll` 取 `vkCreateInstance`,一个找不到该 DLL 的进程会在映像加载期失败、
一个字都印不出来。它印出来了。loader 在,跑了,枚举不到设备 —— 这是关于 ICD 的陈述。
另一侧还有一个事实站在一起:mcpp-index 自己的 `vulkan-tests` 成员在 windows 分片上调用
`vkEnumerateInstanceVersion` 且通过。

顺带排掉了另一个嫌疑:`compat:vulkan` 里的 Khronos loader 能从索引已经携带的源码交叉
构建出可用的 `vulkan-1.dll` —— 265 个导出,与上游 `vulkan-1.def` 逐名吻合;`DllMain`
在;只导入 ADVAPI32、CFGMGR32、KERNEL32 与 msvcrt。描述符那段注记论证的是「Windows 上
的 loader 必须是 DLL」,不是「它构建不出来」。所以这个包随时可以有,只是它不是这个步骤
在等的东西。

留待测量的是:lavapipe 的 Windows 载荷为什么在 mcpp 启动的进程里枚举不出设备。这一条的
一般形式值得记下 —— 被复查的是结论,理由不会。

### 文档

`docs/20` 新增「每条 lane 到得了哪些平台」:三件事同时为真才叫一条 lane 在某个平台上
Expand Down
Loading
Loading