forked from unom/punktfunk
The previous commit built the layer and taught the host to use it, but only the
Arch PKGBUILD carried the files, so every other channel still landed on the
no-game-HDR fallback. This finishes the job.
The packaging scripts now take `--stage`, the DESTDIR the gamescope build script
wrote, instead of a path to one binary. That is the part worth keeping: the next
file this package needs will not require a new flag in four scripts and two
workflows. CI caches the whole staged tree for the same reason. The gs-cache key
already hashes packaging/gamescope/**, which this commit changes, so stale caches
in the old single-file shape cannot be restored into the new layout.
Channels, all of them:
rpm spec gains Source1/Source2 and %files entries
deb build-gamescope-deb.sh copies the layer into the package root
Arch PKGBUILD (previous commit); the sysext extracts the whole usr tree
sysext bazzite takes --gamescope-stage; arch asserts the layer arrived
nix the derivation keeps, renames and rewrites the layer rather than
deleting it with everything else
A missing layer is fatal in every one of them, not best-effort. A package that
carries the compositor without it looks completely healthy and then silently
denies every game an HDR10 swapchain -- the exact failure this whole change
exists to end, so it must not be possible to ship it again by accident.
Two things needed care:
The layer manifest carries an ABSOLUTE library_path baked in at build time, so
every channel has to install the .so at exactly that path. That means literal
/usr/lib/punktfunk, not %{_libdir} (which is /usr/lib64 on Fedora) and not a
Debian multiarch triplet. Nothing links the .so by soname -- the loader dlopens
it by that path -- so multilib has no claim here. The rpm and nix install checks
now read the path back out of the manifest and fail if it names a file the
package does not install, because a manifest pointing at nothing is the silent
shape of this bug.
NixOS has no /usr, so the layer lives inside the gamescope derivation and the
host's path is overridable via PUNKTFUNK_GAMESCOPE_WSI_LAYER_DIR, which the
module sets -- the same posture as PUNKTFUNK_GAMESCOPE_BIN, and documented.
The manifest rewrite moved out of a heredoc into
packaging/gamescope/rewrite-wsi-layer-manifest.py because the FHS builds and the
Nix store both need it and must rename the layer identically; two copies would
drift into a host looking for a name only one of them produces.
Verified: 214 pf-vdisplay tests pass in a linux container, clippy -D warnings and
rustfmt clean, bash -n on all five changed shell scripts, both workflow YAMLs
parse, and the rewrite script was run against a synthetic FROG manifest to
confirm it renames/repoints/regates while preserving the `functions` block --
which is the field that decides whether the layer loads at all.
NOT verified: no nix on this machine, so gamescope.nix, flake.nix and the module
are unevaluated; no gamescope build, no package build of any kind, and no game
has taken an HDR swapchain on glass.
70 lines
2.8 KiB
Python
70 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Rewrite gamescope's generated Vulkan layer manifest so OUR copy of the layer can be installed
|
|
beside the distro's instead of colliding with it.
|
|
|
|
A game nested under gamescope gets its HDR10 swapchain from the FROG WSI layer and from nothing
|
|
else, and that layer speaks `gamescope_swapchain` to the compositor: a layer built for a DIFFERENT
|
|
gamescope makes the compositor reject the client's swapchain_feedback, and every Vulkan client dies
|
|
on a black screen with sound and input and no error. So a compositor we ship needs the layer we
|
|
built beside it — which means two gamescope WSI layers on one box.
|
|
|
|
Three fields make that safe, and the loader is why:
|
|
|
|
* `name` — the Vulkan loader deduplicates implicit layers by name, and with both called
|
|
VK_LAYER_FROG_gamescope_wsi which one wins is unspecified. A distinct name is what lets both sit
|
|
installed at once.
|
|
* `library_path` — made absolute, so resolution never depends on where the loader found the
|
|
manifest.
|
|
* `enable_environment` / `disable_environment` — our own gates, so the host can switch ours ON and
|
|
the distro's OFF in the same session. Sharing ENABLE_GAMESCOPE_WSI would make that impossible.
|
|
|
|
Everything else is passed through untouched, `functions` above all: it names the layer's entry
|
|
points, and a manifest with the wrong ones is a layer that silently never loads.
|
|
|
|
Used by build-punktfunk-gamescope.sh (FHS packaging) and packaging/nix/gamescope.nix (the Nix store),
|
|
which is the point of it being a file rather than a heredoc — the two must not drift.
|
|
|
|
Usage: rewrite-wsi-layer-manifest.py <src.json> <dst.json> <installed-library-path>
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
|
|
LAYER_NAME = "VK_LAYER_PUNKTFUNK_gamescope_wsi"
|
|
ENABLE_VAR = "PUNKTFUNK_GAMESCOPE_WSI"
|
|
DISABLE_VAR = "PUNKTFUNK_GAMESCOPE_WSI_DISABLE"
|
|
|
|
|
|
def main(argv):
|
|
if len(argv) != 4:
|
|
print(__doc__, file=sys.stderr)
|
|
return 2
|
|
src, dst, lib = argv[1:4]
|
|
|
|
with open(src) as f:
|
|
manifest = json.load(f)
|
|
|
|
layer = manifest.get("layer")
|
|
if not isinstance(layer, dict):
|
|
print(f"{src}: no 'layer' object — not a Vulkan layer manifest", file=sys.stderr)
|
|
return 1
|
|
# A manifest that never named the entry points would produce a layer that loads and does
|
|
# nothing, which is indistinguishable on a running box from "this GPU has no HDR".
|
|
if not layer.get("functions") and not layer.get("library_path"):
|
|
print(f"{src}: neither 'functions' nor 'library_path' — refusing to rewrite", file=sys.stderr)
|
|
return 1
|
|
|
|
layer["name"] = LAYER_NAME
|
|
layer["library_path"] = lib
|
|
layer["enable_environment"] = {ENABLE_VAR: "1"}
|
|
layer["disable_environment"] = {DISABLE_VAR: "1"}
|
|
|
|
with open(dst, "w") as f:
|
|
json.dump(manifest, f, indent=2)
|
|
f.write("\n")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv))
|