feat: media bitrate control, HDR (#346)

Fixes: #335 

Still a work-in-progress.

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Wanjohi <elviswanjohi47@gmail.com>
This commit is contained in:
Kristian Ollikainen
2026-09-25 12:13:34 +03:00
committed by GitHub
co-authored by DatCaptainHorse Claude Opus 5 Wanjohi
parent 1c721962f4
commit 0811f57f1a
64 changed files with 15151 additions and 2702 deletions
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env bash
# Builds the guest kernel and installs it at "${KERNEL_OUTPUT}". Runs on the
# host, as yourself: nothing here needs root.
#
# The tree is CachyOS's fork, taken for its patches rather than its config.
# Theirs is a desktop distro config with thousands of modules; this guest has
# no /lib/modules at all. What must hold is kernel/nestri.fragment, merged onto
# whatever .config the tree has and then verified, so a version bump that
# quietly drops an option fails here instead of in a booted box.
set -euo pipefail
: "${KERNEL_GIT:?}"
: "${KERNEL_REF:?}"
: "${KERNEL_SRC:?}"
: "${KERNEL_OUTPUT:?}"
JOBS="${JOBS:-$(nproc)}"
KERNEL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../kernel" && pwd)"
FRAGMENT="${KERNEL_DIR}/nestri.fragment"
SEED="${KERNEL_DIR}/base.config"
# Resolved now, because everything below runs from inside the tree.
mkdir -p "$(dirname "${KERNEL_OUTPUT}")"
KERNEL_OUTPUT="$(cd "$(dirname "${KERNEL_OUTPUT}")" && pwd)/$(basename "${KERNEL_OUTPUT}")"
# ── Source ──────────────────────────────────────────────
if [[ ! -f "${KERNEL_SRC}/Makefile" ]]; then
echo "kernel: cloning ${KERNEL_REF} into ${KERNEL_SRC}"
mkdir -p "$(dirname "${KERNEL_SRC}")"
git clone --depth=1 --branch "${KERNEL_REF}" "${KERNEL_GIT}" "${KERNEL_SRC}"
else
have="$(git -C "${KERNEL_SRC}" describe --tags --exact-match 2>/dev/null || echo unknown)"
if [[ "${have}" != "${KERNEL_REF}" ]]; then
# Not fatal: a bisect or a local patch is a legitimate reason to be off
# the pinned ref, and silently checking it out would throw that away.
echo "kernel: tree is at '${have}', KERNEL_REF pins '${KERNEL_REF}'; building what is there" >&2
fi
fi
cd "${KERNEL_SRC}"
# ── Infinity scheduler (experimental) ───────────────────
# Applied once per tree, and recorded, because `patch -N` on an already patched
# tree does not skip cleanly: it rejects every hunk. The whole series goes in or
# none of it does -- upstream is explicit that a partial series misbehaves -- so
# every patch is dry-run against the stacked result before any is applied.
#
# In this guest only the fair and rt halves do anything. virtio-gpu does not
# use the DRM scheduler, so the gpu patch is compiled out with the rest of
# drivers/gpu/drm/scheduler; it is applied anyway to keep the series whole.
if [[ -n "${KERNEL_INFINITY:-}" ]]; then
: "${INFINITY_GIT:?}" "${INFINITY_REV:?}" "${INFINITY_SERIES:?}" "${INFINITY_WORK:?}"
stamp=".nestri-infinity-rev"
if [[ "$(git -C "${INFINITY_WORK}" rev-parse HEAD 2>/dev/null)" != "${INFINITY_REV}" ]]; then
echo "kernel: fetching infinity-sched ${INFINITY_REV}"
rm -rf "${INFINITY_WORK}"
git init -q "${INFINITY_WORK}"
git -C "${INFINITY_WORK}" fetch -q --depth=1 "${INFINITY_GIT}" "${INFINITY_REV}"
git -C "${INFINITY_WORK}" checkout -q FETCH_HEAD
fi
series_dir="${INFINITY_WORK}/${INFINITY_SERIES}"
[[ -f "${series_dir}/series" ]] || {
echo "kernel: infinity-sched has no series at ${INFINITY_SERIES} for ${KERNEL_REF}" >&2
exit 1
}
have="$(cat "${stamp}" 2>/dev/null || true)"
if [[ "${have}" == "${INFINITY_REV}" ]]; then
echo "kernel: infinity series already applied"
elif [[ -n "${have}" ]]; then
echo "kernel: tree carries infinity ${have}, INFINITY_REV pins ${INFINITY_REV}" >&2
echo "kernel: start the tree over with \`make kernel-clean\`" >&2
exit 1
else
if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then
echo "kernel: ${KERNEL_SRC} has local changes; not applying the series over them" >&2
exit 1
fi
mapfile -t patches < <(grep -v '^[[:space:]]*\(#\|$\)' "${series_dir}/series")
# git apply --check takes the whole list and checks each patch against
# the result of the ones before it, which a per-file `patch --dry-run`
# cannot do.
git apply --check "${patches[@]/#/${series_dir}/}"
for p in "${patches[@]}"; do
echo "kernel: applying ${p}"
# -F 0: zero fuzz. Offsets are fine; a hunk that only fits
# approximately is a scheduler change landing somewhere it was not
# written for.
patch -p1 -N -F 0 --quiet < "${series_dir}/${p}"
done
echo "${INFINITY_REV}" > "${stamp}"
fi
fi
# ── Config ──────────────────────────────────────────────
# A fresh tree has no .config. The seed is a known-good minimal config that
# olddefconfig migrates to whatever version the tree is at; it only saves a
# fresh tree from `make defconfig`, whose driver set is enormous next to what a
# microVM needs. An existing .config is always preferred.
if [[ ! -f .config ]]; then
echo "kernel: seeding .config from kernel/base.config"
cp "${SEED}" .config
fi
echo "kernel: merging kernel/nestri.fragment"
# -m merges without running a config target, so olddefconfig resolves
# dependencies once, in one place.
./scripts/kconfig/merge_config.sh -m .config "${FRAGMENT}" >/dev/null
make olddefconfig >/dev/null
# ── Verify the fragment actually took ───────────────────
# merge_config.sh warns about overridden symbols but exits 0, and olddefconfig
# will happily drop an option whose dependencies are unmet. Neither is loud
# enough for a setting whose failure mode is silent audio, so check the result
# rather than the intent. Both halves count: an option that must be on, and one
# that must be off.
missing=()
total=0
while read -r want; do
total=$((total + 1))
case "${want}" in
CONFIG_*) grep -qx "${want}" .config || missing+=("${want%%=*}") ;;
"# "*) grep -qx "${want}" .config || missing+=("${want:2} (must be off)") ;;
esac
done < <(grep -E '^(CONFIG_[A-Z0-9_]+=|# CONFIG_[A-Z0-9_]+ is not set)' "${FRAGMENT}" \
| sed -E 's/^(CONFIG_[A-Z0-9_]+=[^[:space:]#]+)[[:space:]]*#.*/\1/')
if (( ${#missing[@]} )); then
echo "kernel: these fragment entries did not survive olddefconfig:" >&2
printf '%s\n' "${missing[@]}" >&2
exit 1
fi
echo "kernel: all ${total} fragment entries hold"
# ── Build ───────────────────────────────────────────────
# -march goes in through KCFLAGS because mainline has no Kconfig for
# microarchitecture levels. It is safe for the kernel even at x86-64-v3:
# arch/x86/Makefile passes -mno-sse -mno-mmx -mno-sse2 -mno-avx and friends,
# and gcc applies those as a mask over -march regardless of flag order, so the
# kernel gets v3's integer ISA (BMI2, LZCNT, MOVBE) and its scheduling model
# and never touches a vector register.
make_args=()
if [[ -n "${KERNEL_MARCH:-}" ]]; then
make_args+=("KCFLAGS=-march=${KERNEL_MARCH}")
echo "kernel: building with -march=${KERNEL_MARCH}"
fi
# vmlinux, not bzImage: the guest is booted by an ELF loader with no
# bootloader in the path, and a bzImage is a self-decompressing image behind a
# real-mode setup header, not an ELF.
make -j"${JOBS}" "${make_args[@]}" vmlinux
cp vmlinux "${KERNEL_OUTPUT}"
echo "kernel: installed ${KERNEL_OUTPUT} ($(numfmt --to=iec "$(stat -c %s "${KERNEL_OUTPUT}")"))"
+137 -83
View File
@@ -1,96 +1,150 @@
#!/usr/bin/env bash
# Builds proton-cachyos from the tree proton-fetch.sh laid down. Container-only.
# Builds proton-ge wow64-only and leaves the finished tree in "${PROTON_WORK}/obj/dist".
# Runs on the host, not in a container.
#
# The one thing that matters here is --enable-wow64: it builds wine so that
# 32-bit Windows code runs inside a 64-bit unix process, thunking down to the
# 64-bit host libraries. Without it, Proton needs a complete 32-bit host stack —
# lib32 glibc, a second Mesa built for i686, and a second nescapture layer,
# because a 32-bit game would load the 32-bit Vulkan loader and our 64-bit
# capture layer would be invisible to it. With it, none of that exists.
# It has to run on the host because proton-ge's build is itself container-driven:
# `make` runs outside, and every step runs in the Steam Runtime SDK image, where
# the toolchains live, through the engine it is configured with. There is no
# mode without a container, and a container engine inside `podman build` is
# nested containers, which is a lot of fragile setup for no gain. So the only
# thing this script needs from the host is git, make and the engine. The
# Makefile packages the result afterwards.
#
# The cost is that the distro package cannot be used: proton-cachyos-native is
# packaged without the flag, which is exactly why it depends on lib32-*.
# The one thing we change is the arch list: it becomes wow64-only, and that
# change is the reason this is our own build and not a download. wow64 runs
# 32-bit Windows code inside a 64-bit unix process. Without it Proton needs a
# complete 32-bit host stack: lib32 glibc, a second Mesa built for i686, and a
# second nescapture layer, because a 32-bit game would load the 32-bit Vulkan
# loader and our 64-bit capture layer would be invisible to it. The released
# builds carry an i386 unix side, which is exactly why they need lib32-*.
#
# Everything else is proton-ge's own recipe: the same SDK image, the same flags
# and the same patch set. The one addition is patches/proton-ge/: fixes for the
# places its makefile assumes a 32-bit unix side that wow64 does not have, and
# for things a tag pinned that have since moved out from under it.
set -euo pipefail
: "${GECKO_VER:?}"
: "${MONO_VER:?}"
: "${PROTON_GIT:?}"
: "${PROTON_TAG:?}"
: "${PROTON_WORK:?}"
: "${BUILD_NAME:?}"
ENGINE="${CONTAINER_ENGINE:-podman}"
JOBS="${JOBS:-$(nproc)}"
BUILD_NAME="proton-cachyos"
SRC_DIR="/build/proton-cachyos"
BUILD_DIR="/build/build"
OUT_DIR="/artifacts/proton/usr/share/steam/compatibilitytools.d/${BUILD_NAME}"
[[ -d "${SRC_DIR}" ]] || { echo "no source tree — proton-fetch.sh did not run"; exit 1; }
mkdir -p "${PROTON_WORK}"
PROTON_WORK="$(cd "${PROTON_WORK}" && pwd)"
SRC="${PROTON_WORK}/src"
OBJ="${PROTON_WORK}/obj"
STAMP_TAG="${PROTON_WORK}/.tag"
STAMP_PATCHED="${PROTON_WORK}/.patched"
PATCH_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../patches/proton-ge" && pwd)"
# ── Toolchain wrappers ──────────────────────────────────
# Proton's build calls the compiler by GNU triplet. Arch's gcc does not install
# under those names, so stand in for them. The i686 set is generated too: with
# wow64 nothing should reach for it, and if something does, failing on a missing
# 32-bit header beats silently building a 32-bit unix library we then have to
# ship libraries for.
WRAP=/build/wrappers
rm -rf "$WRAP" && mkdir -p "$WRAP"
_wrappers() {
local arch="$1" gccflag="$2" ldflag="$3" asflag="$4" stripfmt="$5"
local l t
for l in ar ranlib nm; do
ln -sf "/usr/bin/gcc-${l}" "${WRAP}/${arch}-pc-linux-gnu-${l}"
# Two builds in one tree do not fail cleanly. They race on the same objects and
# leave half-written files that a later build trusts. A failed make also keeps
# running its in-flight jobs for a while after it reports the error, so the
# first build is often still running when the second one starts.
exec 9>"${PROTON_WORK}/.lock"
flock -n 9 || { echo "proton: another build is using ${PROTON_WORK}" >&2; exit 1; }
# ccache and cargo's downloads are kept outside src/ and obj/, so a new tag or
# FORCE_REBUILD throws away the build and keeps the parts that are correct to
# reuse. proton-ge's makefile mounts both into the container from these
# variables.
export CCACHE_DIR="${PROTON_WORK}/ccache"
export CARGO_HOME="${PROTON_WORK}/cargo"
mkdir -p "${CCACHE_DIR}" "${CARGO_HOME}"
if [[ -n "${FORCE_REBUILD:-}" || "$(cat "${STAMP_TAG}" 2>/dev/null)" != "${PROTON_TAG}" ]]; then
echo "proton: fresh tree for ${PROTON_TAG}"
rm -rf "${SRC}" "${OBJ}" "${STAMP_TAG}" "${STAMP_PATCHED}"
fi
# ── Fetch ───────────────────────────────────────────────
if [[ ! -e "${STAMP_TAG}" ]]; then
rm -rf "${SRC}"
git clone --branch "${PROTON_TAG}" --depth=1 "${PROTON_GIT}" "${SRC}"
# No --depth here: submodules are pinned to commits that are often not a
# branch tip. --filter=tree:0 keeps the download down instead.
git -C "${SRC}" submodule update --init --filter=tree:0 --recursive
echo "${PROTON_TAG}" > "${STAMP_TAG}"
fi
# The SDK image is pinned by proton-ge's own makefile, per tag. Asking it keeps
# the patch step below and the build on the same image.
SDK_IMAGE="$(make --silent --no-print-directory -f "${SRC}/Makefile.in" \
SRCDIR="${SRC}" get-steamrt-image)"
# ── Patch ───────────────────────────────────────────────
# The patch script edits the tree in place and is not idempotent: it resets
# some submodules first and not others. So a tree is patched once, and one that
# was interrupted halfway is reset to the commits the tag pins before trying
# again.
#
# It is run in the SDK image rather than on the host, so it does not depend on
# the host's python, patch or wget.
#
# The script carries on past a patch that does not apply and exits 0 anyway.
# The upstream instructions are to grep its output for failures, so that is
# what happens here. The alternative is an image that looks fine and is missing
# a fix.
#
# A build tree does not survive its source being re-patched. Changing
# Makefile.in re-syncs every component's source copy, but a component's
# configure step depends on that sync order-only, so it does not rerun, and its
# old build directory is left pointing at generated autotools files the sync
# just removed. So patching starts obj/ over too. ccache keeps that cheap.
if [[ ! -e "${STAMP_PATCHED}" ]]; then
rm -rf "${OBJ}"
git -C "${SRC}" reset -q --hard
git -C "${SRC}" submodule foreach -q --recursive 'git reset -q --hard && git clean -qfdx'
"${ENGINE}" run --rm -v "${SRC}:${SRC}" -w "${SRC}" "${SDK_IMAGE}" \
./patches/protonprep-valve-staging.sh 2>&1 | tee "${PROTON_WORK}/patch.log"
if grep -Ei 'hunk .* failed|saving rejects|can.t find file|malformed patch|skipping patch|^error' \
"${PROTON_WORK}/patch.log"; then
echo "proton: patches did not apply cleanly, see ${PROTON_WORK}/patch.log" >&2
exit 1
fi
# Ours go on top. They are paths from the root of the tree, submodules
# included. `git apply` fails outright on a patch that no longer applies,
# which is what a tag bump should do: each one says why it exists, so the
# question is only whether upstream fixed it.
for p in "${PATCH_DIR}"/*.patch; do
[[ -e "$p" ]] || continue
echo "proton: applying $(basename "$p")"
git -C "${SRC}" apply "$p"
done
for t in gcc g++; do
printf '#!/usr/bin/bash\n/usr/bin/%s %s "$@"\n' "$t" "$gccflag" \
> "${WRAP}/${arch}-pc-linux-gnu-${t}"
chmod 755 "${WRAP}/${arch}-pc-linux-gnu-${t}"
done
printf '#!/usr/bin/bash\n/usr/bin/ld %s "$@"\n' "$ldflag" > "${WRAP}/${arch}-pc-linux-gnu-ld"
printf '#!/usr/bin/bash\n/usr/bin/as %s "$@"\n' "$asflag" > "${WRAP}/${arch}-pc-linux-gnu-as"
printf '#!/usr/bin/bash\n/usr/bin/strip -F %s "$@"\n' "$stripfmt" > "${WRAP}/${arch}-pc-linux-gnu-strip"
chmod 755 "${WRAP}/${arch}-pc-linux-gnu-"{ld,as,strip}
}
_wrappers x86_64 "-m64" "-melf_x86_64" "--64" "elf64-x86-64"
_wrappers i686 "-m32" "-melf_i386" "--32" "elf32-i386"
export PATH="${WRAP}:${PATH}"
touch "${STAMP_PATCHED}"
fi
# ── Configure ───────────────────────────────────────────
# configure.sh refuses an in-tree build, and it test-runs the SDK image to work
# out how the engine maps file ownership, so it is also where a broken engine
# setup shows up first.
mkdir -p "${OBJ}"
if [[ ! -e "${OBJ}/Makefile" ]]; then
(cd "${OBJ}" && "${SRC}/configure.sh" \
--build-name="${BUILD_NAME}" \
--container-engine="${ENGINE}")
fi
# ── Build ───────────────────────────────────────────────
# -march=nocona matches the distro packaging: Proton has to run on whatever CPU
# the guest is given, and the VMM does not promise a feature level.
export CFLAGS="-O3 -march=nocona -mtune=core-avx2"
export CXXFLAGS="${CFLAGS}"
export RUSTFLAGS="-C opt-level=3 -C target-cpu=nocona"
export LDFLAGS="-Wl,-O1,--sort-common,--as-needed"
export RUSTUP_TOOLCHAIN=stable
# ARCHS drops i386-unix, which leaves wine configured for x86_64 unix with an
# i386 PE side. That is wow64. Every component rule is gated on ARCHS, so the
# 32-bit unix builds of everything else go with it. ENABLE_WOW64 makes the
# proton script ask wine for a wow64 prefix. proton-ge ships it as a switch
# but never turns it on.
#
# A command-line variable reaches the container build too: the outer make
# hands its overrides to the inner one.
#
# SOURCE_DATE_EPOCH is the tag's commit time rather than now, so two builds of
# one tag stamp the same dates into their output.
make -C "${OBJ}" \
J="${JOBS}" \
ARCHS="i386-windows x86_64-windows x86_64-unix" \
ENABLE_WOW64=1 \
SOURCE_DATE_EPOCH="$(git -C "${SRC}" log -1 --format=%ct)" \
dist
mkdir -p "${BUILD_DIR}"
cd "${BUILD_DIR}"
ROOTLESS_CONTAINER="" \
"${SRC_DIR}/configure.sh" \
--container-engine="none" \
--proton-sdk-image="" \
--build-name="${BUILD_NAME}" \
--without-extras=all \
--without-vklayers=all \
--without-steamrt-depends \
--without-tts \
--without-nvidia-libs \
--enable-wow64
# The top-level make is serial by design; SUBJOBS is what it hands to each
# component's build.
SUBJOBS="${JOBS}" make -j1 dist
# ── Install ─────────────────────────────────────────────
mkdir -p "${OUT_DIR}"
cp -a "${BUILD_DIR}/dist/." "${OUT_DIR}/"
# Debug symbols in the bundled PE runtimes are dead weight in a guest image.
cd "${OUT_DIR}/files"
find "share/wine/gecko/wine-gecko-${GECKO_VER}-x86" -name '*.dll' -o -name '*.exe' 2>/dev/null \
| xargs -r i686-w64-mingw32-strip --strip-debug 2>/dev/null || true
find "share/wine/gecko/wine-gecko-${GECKO_VER}-x86_64" -name '*.dll' -o -name '*.exe' 2>/dev/null \
| xargs -r x86_64-w64-mingw32-strip --strip-debug 2>/dev/null || true
find "share/wine/mono/wine-mono-${MONO_VER}" -name '*.dll' -o -name '*.exe' 2>/dev/null \
| xargs -r i686-w64-mingw32-strip --strip-debug 2>/dev/null || true
rm -rf "${BUILD_DIR}"
echo "proton: installed to ${OUT_DIR}"
echo "proton: built ${OBJ}/dist"
-57
View File
@@ -1,57 +0,0 @@
#!/usr/bin/env bash
# Fetches proton-cachyos' source and its bundled runtimes. Container-only.
#
# Deliberately its own script, and its own layer: the submodule checkout runs
# well past ten minutes, and it must not be redone every time a build flag or a
# missing dependency changes. Keep everything that can fail *fast* in
# proton-build.sh instead.
set -euo pipefail
: "${PROTON_GIT:?}"
: "${PROTON_TAG:?}"
: "${GECKO_VER:?}"
: "${MONO_VER:?}"
: "${XALIA_VER:?}"
SRC_DIR="/build/proton-cachyos"
git clone --branch "${PROTON_TAG}" --depth=1 "${PROTON_GIT}" "${SRC_DIR}"
cd "${SRC_DIR}"
# Relative submodule paths resolve against origin, so it has to be the real URL
# even though we cloned by tag.
git remote set-url origin "${PROTON_GIT}"
# No --depth here: submodules are pinned to commits that are often not a branch
# tip. --filter=tree:0 keeps the download down instead.
git submodule update --init --filter=tree:0 --recursive
# Still needed with wow64: these are PE-side, and a 32-bit Windows program wants
# the 32-bit gecko and mono regardless of how wine is built.
mkdir -p contrib
for url in \
"https://dl.winehq.org/wine/wine-gecko/${GECKO_VER}/wine-gecko-${GECKO_VER}-x86.tar.xz" \
"https://dl.winehq.org/wine/wine-gecko/${GECKO_VER}/wine-gecko-${GECKO_VER}-x86_64.tar.xz" \
"https://github.com/madewokherd/wine-mono/releases/download/wine-mono-${MONO_VER}/wine-mono-${MONO_VER}-x86.tar.xz" \
"https://github.com/madewokherd/xalia/releases/download/xalia-${XALIA_VER}/xalia-${XALIA_VER}-net48-mono.zip" \
; do
curl -fL --retry 3 -o "contrib/$(basename "$url")" "$url"
done
# Proton's cargo rule runs `cargo build --locked --offline`, so every crate has
# to be in CARGO_HOME before the build starts — including the git dependencies,
# which is what the "you are in the offline mode" failure is really saying. The
# error names a URL that is perfectly reachable; the build simply refuses to go
# out and get it.
#
# gst-plugins-rs is the only cargo component in the tree. Both targets are
# fetched: wow64 should mean nothing builds the i386 unix side, but a fetch is
# metadata only and costs almost nothing next to being wrong about that.
#
# CARGO_HOME is left at its default so it lands in this layer and the build
# layer inherits it.
export CARGO_NET_GIT_FETCH_WITH_CLI=true
export RUSTUP_TOOLCHAIN=stable
cd "${SRC_DIR}/gst-plugins-rs"
cargo fetch --locked --target x86_64-unknown-linux-gnu
cargo fetch --locked --target i686-unknown-linux-gnu
echo "proton: source at ${SRC_DIR}"