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
+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"