Skip to content

wsl-gl-host-link 强制 GALLIUM_DRIVER=d3d12,但 xim:mesa 的包内没有 d3d12_dri.so —— WSL2 上 GL 程序报 glx: failed to create drisw screen #382

Description

@FarnaHerry

环境

  • WSL2(内核 6.6.87.2-microsoft-standard-WSL2),/dev/dxg 存在
  • mcpp 2026.8.8.2(mcpp run 已应用 subos_info 环境变量)
  • 图形栈:compat.glx-runtime@2026.08.08 → xim:graphics;mesa 25.0.7.1;wsl-gl-host-link@0.1.0
  • 复现项目:任意 GLFW 应用(如 tinynext),mcpp 构建,mcpp run 运行

问题描述
WSL2 上,wsl-gl-host-link 配方(pkgs/w/wsl-gl-host-link.lua)在 subos 里声明 GALLIUM_DRIVER=d3d12,前提是 mesa 包里带有 d3d12 gallium 驱动模块。但实际上包内没有:xim-x-mesa/25.0.7.1/lib/dri/ 只有 kms_swrast_dri.so、swrast_dri.so、zink_dri.so、nouveau_dri.so、radeonsi_dri.so(全是 libdril_dri.so 的符号链接)。Mesa 被强制指定 d3d12 驱动,找不到驱动模块,又不回退到 swrast → GL 上下文创建失败:

glx: failed to create drisw screen
(退出码 255)

复现步骤
mcpp build
mcpp run

→ "glx: failed to create drisw screen",退出码 255

证据

  1. 已安装的包内容 ~/.mcpp/registry/data/xpkgs/xim-x-mesa/25.0.7.1/lib/dri/:
    kms_swrast_dri.so -> libdril_dri.so
    libdril_dri.so
    nouveau_dri.so -> libdril_dri.so
    radeonsi_dri.so -> libdril_dri.so
    swrast_dri.so -> libdril_dri.so
    zink_dri.so -> libdril_dri.so
  2. 没有 d3d12_dri.so。(mesa.lua 注释声称 lib/dri/ 下有"十二个驱动模块",实际发货包里只有六个条目,且配方里驱动列表是 llvmpipe/softpipe/radeonsi/nouveau/zink——d3d12 压根没在列表里。)
  3. wsl-gl-host-link.lua 第 42–43 / 234–235 行断言与此相反:"libd3d12core.so 由 d3d12_dri.so dlopen,而 d3d12_dri.so 是我们的文件"、"d3d12_dri.so 带的 DT_RPATH 以 subos lib 目录结尾(已在发货包上验证)"——这个前提对当前 mesa 包不成立。
  4. 已按上述复现;并确认强制驱动就是根因:同一二进制、同一 hermetic loader,GALLIUM_DRIVER 不设(或设 llvmpipe)时运行正常(llvmpipe/softpipe);设 GALLIUM_DRIVER=d3d12 时退出 255。把系统 /usr/lib64/dri/d3d12_dri.so 手动加进 LIBGL_DRIVERS_PATH 也无法初始化(宿主的 libd3d12core.so 跑不了 mcpp 私有 glibc 2.39;.wsl-host 里 glibc floor 记录为 unknown)。

根因
xim:graphics 栈内的协调缺口:wsl-gl-host-link 依赖 mesa,但 mesa 的配方/包并没有用 -Dgallium-drivers=d3d12 构建 d3d12 驱动。强制 GALLIUM_DRIVER=d3d12 把"驱动缺失"变成硬失败,而不是优雅回退到 llvmpipe。

次要观察
wsl-gl-host-link.lua 里写的逃生通道("用户自己导出 GALLIUM_DRIVER=llvmpipe 会保留")在 mcpp run 应用 subos env 时不生效:export GALLIUM_DRIVER=llvmpipe; mcpp run 仍然失败,即 subos 的 set 覆盖了用户预先导出的值。(这可能属于 mcpp-community/mcpp 的 subos_info 应用逻辑。)

建议修复

  • 用 -Dgallium-drivers=d3d12 构建 mesa,并在 lib/dri/ 里带上 d3d12_dri.so(修 WSL2 上的强制驱动路径);或
  • 让 wsl-gl-host-link 在驱动模块缺失时不要强制 GALLIUM_DRIVER=d3d12,让 Mesa 回退到 llvmpipe(与非 WSL 主机的 no-op 哨兵逻辑一致);或
  • 两者都做:仅当模块存在时才强制。

相关文件

  • pkgs/m/mesa.lua(驱动集合;包来自 xlings-res/mesa 25.0.7.1)
  • pkgs/w/wsl-gl-host-link.lua(假设 d3d12_dri.so 存在)

Activity

  1. speak-agent commented on Aug 8, 2026

    @speak-agent
    Member

    Thanks — this report was accurate and the evidence was exactly what was needed. Both halves are now fixed, in two different places.

    Your claim, verified

    25.0.7.1 has no d3d12 anywhere. Not just the missing d3d12_dri.so you found — it is absent from libgallium's GALLIUM_DRIVER selector table too (symbols are hidden, so the name table is the right probe):

    selector table d3d12_dri.so libgallium
    mesa 25.0.7.1 iris llvmpipe radeonsi softpipe ✗ 31 MB
    mesa 25.0.7.2 d3d12 iris llvmpipe radeonsi softpipe ✓ 44 MB

    So forcing GALLIUM_DRIVER=d3d12 on it could only hard-fail. Your root-cause statement was right.

    What shipped

    1. mesa 25.0.7.2 (openxlings/xim-pkgindex#563) — adds d3d12 and iris. That took five prerequisite packages that did not exist or were not wired in (directx-headers, spirv-tools, wayland-protocols, an llvm-dev rebuilt with AMDGPU, plus a gcc fix), and a patch for mesa 25.0.7 against glibc 2.44.

    Verified on the payload: libgallium → libLLVM.so.20.1 (ours, not the host's 18), and none of the build-only libraries reached it.

    2. wsl-gl-host-link no longer forces unconditionally (openxlings/xim-pkgindex#565) — your suggestion (c). It checks <subos>/usr/lib/dri/d3d12_dri.so and, when absent, warns with the remedy and leaves GL on llvmpipe. The check reads the subos view rather than mesa's payload, because that directory is an xvm.files symlink to the active mesa — so it asks "what will actually be loaded".

    This matters beyond your case: the sentinel declares a bare xim:mesa ("whatever mesa this home resolves"), so shipping a fixed mesa is not by itself a fix for the sentinel.

    Your secondary observation was the sharpest one

    export GALLIUM_DRIVER=llvmpipe; mcpp run 仍然失败

    You were right, and it invalidated the recipe's own safety argument. The comment said:

    A user who exported GALLIUM_DRIVER themselves keeps it (UC-1) … and that escape is why forcing the value here is safe.

    subos.env has exactly two ops — set and prepend (xlings src/core/subos/manifest.cppm:45-46) — and neither is conditional. There was never an escape hatch; it existed only in that comment. Filed as openxlings/xlings#508 for a default (set-if-unset) op. Until that lands a package cannot offer "the user's value wins", which is why #565 forces less rather than promising an override it cannot deliver.

    Two of your notes I also corrected

    • mesa.lua claimed "twelve driver modules" under lib/dri/. 25.0.7.1 ships 6 entries, 25.0.7.2 ships 8. Removed the number.
    • wsl-gl-host-link.lua's "d3d12_dri.so carries a DT_RPATH ending in the subos lib directory (verified on the shipped payload)". The RPATH half is true — it is a symlink to libdril_dri.so, whose RPATH ends in <subos>/lib on both payloads. The file half was never true of 25.0.7.1, so nothing about it had been verified. Now stated accurately.

    Caveat on my side

    I have no WSL2 and no Intel GPU here, so d3d12 and iris are in the payload with a clean dependency closure but unexercised. If you can retest on 25.0.7.2 I would like to know whether the GPU path actually initialises — that is the one result this machine cannot produce.

  2. speak-agent commented on Aug 8, 2026

    @speak-agent
    Member

    可以验证了,而且不需要 mcpp 新版本

    主症状已在包侧解决(openxlings/xim-pkgindex#565)。mcpp 只是把 subos_info.envs 里写的东西应用到子进程 —— 配方不再无条件声明那个变量,症状就没有了。当前发布的 mcpp 2026.8.8.2 即可。

    包侧现在的行为

    wsl-gl-host-link 改成先看驱动在不在,再决定要不要强制:

    local dri_d3d12 = path.join(system.subos_sysrootdir(), "usr","lib","dri","d3d12_dri.so")
    if not os.isfile(dri_d3d12) then
        -- 不声明 GALLIUM_DRIVER,让 mesa 自己回落到 llvmpipe
        return true
    end
    subos.env{ var = "GALLIUM_DRIVER", op = "set", value = "d3d12", binding = tag }

    检查读的是 subos 视图而不是某个已装 payload —— usr/lib/dri 是指向当前生效 mesa 的 xvm.files 符号链接,所以它问的是「实际会被加载的是什么」。

    怎么验证

    xlings update                      # 取到 #565 之后的配方
    mcpp build && mcpp run

    判据(按可信度排序,建议至少看前两条):

    1. 装机日志。mesa 没带 d3d12 时应出现:
      wsl-gl-host-link: this is a WSL host, but the active mesa has no d3d12_dri.so -- NOT forcing GALLIUM_DRIVER=d3d12
      —— 有这一行,就说明新配方生效了。

    2. 变量确实没被设:

      mcpp run -- sh -c 'echo "GALLIUM_DRIVER=[$GALLIUM_DRIVER]"'

      期望是空。若仍打印 d3d12,说明索引还没刷到 mcpp clean has no middle ground between "keep everything" and "remove target/": fingerprint directories that are no longer current are never collected #565(先 xlings update)。

    3. 程序跑起来:不再是 glx: failed to create drisw screen / 退出码 255。

    4. 要 GPU 加速:装 mesa >= 25.0.7.2(带 d3d12 驱动),此时上面第 1 条会变成
      GALLIUM_DRIVER=d3d12 (d3d12_dri.so present),且第 2 条会打印 d3d12。

    你报的「次要观察」:逃生通道 —— 仍未修,且不该由 mcpp 单方面修

    export GALLIUM_DRIVER=llvmpipe; mcpp run 仍然失败,即 subos 的 set 覆盖了用户预先导出的值

    观察属实。我曾在 mcpp 侧把 set 改成「用户已 export 就让位」,又撤回了,理由两条:

    1. set 与「默认值」是两种真实不同的意图。 有些变量 subos 必须说了算 —— 指向它自己 loader 配置的那类,用户 shell 里一个陈旧值就能把环境弄坏。把两者塌成一个,等于从此无法表达前者。
    2. op 词汇表是 xlings 的契约,mcpp 是消费方。 消费方悄悄给一个 op 加第二种含义,会让同一个 subos 因为由谁启动而行为不同。

    所以正解是 xlings 新增一个 op(比如 set-if-unset / default:声明默认值,用户可覆盖),配方改用它,mcpp 认它。有一点必须提醒:mcpp 目前会丢弃未知 op,所以那个 op 必须两侧同时到位,xlings 单方面加了等于没加。

    不过就本 issue 而言,#565 之后逃生通道已经不是必需的了 —— 驱动不在时根本不会去设那个变量。

    顺带修掉的一个缺陷(与本 issue 无关,但在同一段代码里)

    查这条时发现:prepend 会把调用方的值整个丢掉。解析结果是替换子进程变量的(作为 extraEnv 进去),而 prepend 只发出声明值 —— 对 PATH 形状的变量,丢掉的是用户的整条搜索路径。已修(#383,已合入 main),会随下一个版本发布。它不影响本 issue 的验证。

  3. speak-agent commented on Aug 8, 2026

    @speak-agent
    Member

    Follow-up on your secondary observation — you were right that it is the application logic, and it is on the mcpp side

    这可能属于 mcpp-community/mcpp 的 subos_info 应用逻辑

    That guess was correct. I measured xlings' own application and it is conditional in all four backends:

    backend code
    POSIX : "${VAR:=value}"; export VAR;
    fish if not set -q VAR; set -gx VAR …; end
    pwsh if (-not $env:VAR) { $env:VAR = … }
    in-process else if (existing.empty()) { set_env_variable(...) }

    Verified by injecting a synthetic op = "set" declaration and entering the subos with the variable already exported:

    generated:  : "${XLINGS_SET_PROBE:=PACKAGE_VALUE}"; export XLINGS_SET_PROBE;
    
    $ XLINGS_SET_PROBE=USER_VALUE xlings subos use default --sandbox --cmd 'echo $XLINGS_SET_PROBE'
      XLINGS_SET_PROBE=USER_VALUE
    

    The user's value wins under xlings subos use. Under mcpp run it did not, in your measurement — so op = "set" is being applied unconditionally when mcpp reads subos_info.

    The contract to match: op = "set" means set if unset — it is a default, not an override. (The name is genuinely misleading; I misread it the same way you did, and I filed and then closed an xlings issue on the strength of that misreading — openxlings/xlings#508.) op = "prepend" composes with an existing value.

    Fixing that on the mcpp side restores the escape hatch you were looking for, independently of the driver-presence fix that already shipped.

  4. speak-agent commented on Aug 8, 2026

    @speak-agent
    Member

    结论:主症状已在包侧修复,关闭。你报的「次要观察」是一条独立的 mcpp 缺陷,已移交 #397(C-7)单独追踪。

    主症状

    xim-pkgindex #563(mesa 25.0.7.2,带 d3d12 与 iris)+ #565(wsl-gl-host-link 改成先看 <subos>/usr/lib/dri/d3d12_dri.so 在不在,不在就不声明 GALLIUM_DRIVER,让 mesa 自己回落 llvmpipe)。你给的三条证据全部成立,根因判断也对。

    ⚠️ 但存量机器不会自动好转 —— 这一条比原先估计的更硬,请照下面处理

    核验时发现两层原因,任何一层单独成立就够:

    1. config() 在你的机器上根本不会再跑。 xlings src/core/xim/commands.cppm:748-750 的注释是自述:install 把已存在的 payload 目录当成「已安装」,连同它的 config() hook 一起跳过。而 wsl-gl-host-link 的版本没变(pkgs/w/wsl-gl-host-link.lua,latest -> 0.1.0;mcpp clean has no middle ground between "keep everything" and "remove target/": fingerprint directories that are no longer current are never collected #565 只改了配方体、没有 bump)⇒ 新配方对已装过的机器一行都不执行。
    2. 就算重跑了,旧声明也留着。 新配方在缺驱动时走的是「一条 subos.env 都不发」的早退,而 xlings 的记录函数第一行就是 installer.cppm:1549 的 if (declarations.empty()) return true; —— 空批次不触发整节替换。installer.cppm:1626-1630 的注释写明这是有意为之:「A package that stops declaring env entirely is NOT handled here … the stale section is removed by uninstall or by superseded」。

    所以你的 .xlings.json 里 subos_info.envs["wsl-gl-host-link@0.1.0"] 的 GALLIUM_DRIVER=d3d12 还在。 三条路任选:

    # (a) 重装该包(会重新执行 config())
    xlings uninstall wsl-gl-host-link && xlings install wsl-gl-host-link -y
    
    # (b) 直接编辑,删掉那一条声明
    $EDITOR ~/.xlings/subos/default/.xlings.json     # 找 subos_info.envs → wsl-gl-host-link@0.1.0
    
    # (c) 装 mesa >= 25.0.7.2,让强制变成正当的(此时你会真的走到 d3d12 GPU 路径)

    验证用 mcpp run -- sh -c 'echo "[$GALLIUM_DRIVER]"',期望是空 —— 别看装机日志,日志只说明新配方跑过,不说明旧声明已被清掉。

    你的「次要观察」是这轮里最尖锐的一条,已单独立项

    export GALLIUM_DRIVER=llvmpipe; mcpp run 仍然失败,即 subos 的 set 覆盖了用户预先导出的值。这可能属于 mcpp 的 subos_info 应用逻辑。

    猜得对,而且它至今未修。src/xlings/subos_info.cppm:256-281 取了 ambient 值但对 set 完全不用:

    auto amb = ambient_of(d.var);
    if (d.op == "set") { … out.emplace_back(d.var, value); continue; }   // amb 被丢弃

    而 xlings 的四个后端全是条件赋值(xlings/src/core/subos.cppm:1012 POSIX : "${VAR:=value}" / :1120 fish if not set -q / :1143 pwsh if (-not $env:VAR) / :1166 in-process else if (existing.empty()))。后果是同一个 subos,xlings subos use 进去你的 export 保留,mcpp run 进去被覆盖 —— 正是 subos_info.cppm:262-272 那段注释自己声称要避免的分歧,而分歧是 mcpp 造成的。

    完整分析、修法与三次反复的历史(73ab179 → 1cc1052 → c1e9360)见 #397 C-7。

    感谢这份报告 —— 证据质量很高,它同时暴露了包侧、xlings 侧和 mcpp 侧三处问题。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions