mirror of
https://github.com/nestriness/nestri.git
synced 2026-11-03 22:22:39 +02:00
fix: OpenGL games draw, Chromium games present, Proton makes non-ASCII file names (#401)
Games that showed a black screen with sound now render. Found by testing on an AMD host: none of these were GPU-vendor specific. **OpenGL games (Dome Keeper, Godot): fixed and confirmed.** - `c017462`: the guest's Mesa had no EGL/GLX frontends, so a GL game had nothing to load and never created a device. The image now ships them, with glvnd's vendor file, so GL reaches zink and the capture layer sees the frames. - `5f2ccdf`: `vm.overcommit_memory=1`. The kernel default refused Dome Keeper's 9 GiB reservation on an 8 GiB box before a byte was used. **Chromium / NW.js games (Sheepy) stalled after one frame: fixed.** - `a73c0c4`: nescope answers every commit with frame callbacks and feedback, not only commits from a surface it maps to a window. Chromium's GPU process draws for a window another process owns. Wine's Wayland driver gives that surface no role, so its FIFO present waited forever in the driver. - `23ba9b5`: a window is matched by its surface, not its xdg toplevel, so Xwayland windows are paced too. **Proton could not create non-ASCII file names: patch added, build pending.** - `6770684`: Steam starts games with `LC_ALL=C`, and Wine reads that as ASCII for every Unix file name. NW.js stops unpacking its app at `media/• drum loop….webm` and shows an empty window. - The patch reads an ASCII locale as UTF-8. Proton now takes patches from `build/patches/proton/<submodule>/`, and a patched image is tagged `-nN` (`11.0-20261005-n1`). - Nothing points at that tag yet; hosts move to it in a separate change, after it is built and tested. Not covered: Metro 2033 Redux's report looks like the frame-callback stall, but it was not retested.
This commit is contained in:
@@ -100,22 +100,27 @@ impl CompositorHandler for NescopeState {
|
||||
s
|
||||
};
|
||||
|
||||
let Some(window) = self
|
||||
// Matched on the window's surface, not its xdg toplevel: an Xwayland
|
||||
// window has no toplevel.
|
||||
let window = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|w| {
|
||||
w.toplevel()
|
||||
.map(|t| t.wl_surface() == &root)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.cloned()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
.find(|w| w.wl_surface().is_some_and(|s| *s == root))
|
||||
.cloned();
|
||||
if let Some(window) = &window {
|
||||
window.on_commit();
|
||||
self.game_frame_count += 1;
|
||||
}
|
||||
|
||||
window.on_commit();
|
||||
self.game_frame_count += 1;
|
||||
self.present_now(&root, &window);
|
||||
// Every commit is answered, mapped window or not. A Vulkan client
|
||||
// presenting FIFO waits in the driver for this frame callback, and
|
||||
// not every surface a game draws to is a window here: Chromium's GPU
|
||||
// process draws for a window another process owns, which Wine's
|
||||
// Wayland driver gives a surface of its own with no role. Unanswered,
|
||||
// that game presents one frame and waits forever. There is no screen
|
||||
// here to pace against -- capture paces itself -- so answering every
|
||||
// commit costs nothing a mapped window does not already get.
|
||||
self.present_now(&root);
|
||||
}
|
||||
|
||||
fn destroyed(&mut self, surface: &WlSurface) {
|
||||
|
||||
@@ -735,13 +735,14 @@ impl NescopeState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn present_now(&mut self, root: &WlSurface, window: &Window) {
|
||||
pub(crate) fn present_now(&mut self, root: &WlSurface) {
|
||||
let output = self.output.clone();
|
||||
let now = self.clock.now();
|
||||
self.presentation_seq += 1;
|
||||
|
||||
let mut fb = OutputPresentationFeedback::new(&output);
|
||||
window.take_presentation_feedback(
|
||||
take_presentation_feedback_surface_tree(
|
||||
root,
|
||||
&mut fb,
|
||||
|_, _| Some(output.clone()),
|
||||
|_, _| wp_presentation_feedback::Kind::Vsync,
|
||||
|
||||
@@ -219,11 +219,26 @@ fn devices() {
|
||||
/// or Wine, and the process dies — here as a `wineserver crashed` with the game
|
||||
/// already gone. SteamOS and every distribution that packages Proton raise it
|
||||
/// to this value for that reason.
|
||||
const SYSCTLS: &[(&str, &str, &str)] = &[(
|
||||
"vm/max_map_count",
|
||||
"2147483642",
|
||||
"a game that maps more than 65530 regions (Unreal Engine 5 does) dies on a failed allocation",
|
||||
)];
|
||||
/// - `vm.overcommit_memory`: **the kernel's default refuses any one allocation
|
||||
/// larger than RAM plus swap**, and a box has no swap, so the ceiling is the
|
||||
/// box's size: 8 GiB at 1080p. Engines and drivers reserve address space far
|
||||
/// past what they touch, and a reservation is refused before a byte of it is
|
||||
/// used — Dome Keeper asked for 9 GiB on an 8 GiB box and never started
|
||||
/// (2026-10-14). `1` grants every request and leaves the OOM killer to act on
|
||||
/// what is actually used; in a box that is the game, since this process has
|
||||
/// already refused it.
|
||||
const SYSCTLS: &[(&str, &str, &str)] = &[
|
||||
(
|
||||
"vm/max_map_count",
|
||||
"2147483642",
|
||||
"a game that maps more than 65530 regions (Unreal Engine 5 does) dies on a failed allocation",
|
||||
),
|
||||
(
|
||||
"vm/overcommit_memory",
|
||||
"1",
|
||||
"a game that reserves more address space than the box has RAM is refused it and does not start",
|
||||
),
|
||||
];
|
||||
|
||||
/// How many files a process may hold open, which every process in the box
|
||||
/// inherits from here.
|
||||
@@ -678,6 +693,23 @@ mod tests {
|
||||
assert!("2147483642".parse::<u64>().unwrap() > 65530 * 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_reservation_bigger_than_the_box_is_granted() {
|
||||
let root = scratch("overcommit");
|
||||
std::fs::create_dir_all(root.join("vm")).unwrap();
|
||||
std::fs::write(root.join("vm/overcommit_memory"), "0\n").unwrap();
|
||||
|
||||
apply_sysctls(&root);
|
||||
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(root.join("vm/overcommit_memory"))
|
||||
.unwrap()
|
||||
.trim(),
|
||||
"1"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
/// A box on a kernel without the knob still boots; the cost is said, not
|
||||
/// raised.
|
||||
#[test]
|
||||
|
||||
+13
-5
@@ -80,6 +80,13 @@ ARG MESA_GIT=https://gitlab.freedesktop.org/mesa/mesa.git
|
||||
ARG MESA_COMMIT=a4a47003a194effad8cabe727f74985e9417476f
|
||||
ARG JOBS=
|
||||
|
||||
# OpenGL is zink: GL on top of whichever Vulkan driver the box has, radv over
|
||||
# native context or the forwarded NVIDIA one, so one build serves both. Its
|
||||
# frontends have to be on for any of it to be reachable. With EGL, GLX, GBM and
|
||||
# GLES off, a GL game found no GL at all and ran with sound and a black screen,
|
||||
# which is what three alpha testers saw. GLX needs the x11 platform for
|
||||
# Xwayland's clients, and Xwayland needs GBM to give them DRI3.
|
||||
#
|
||||
# Our patches to the amdgpu native-context path, applied on top of the pinned
|
||||
# commit. They are not cosmetic: upstream's winsys re-queries device-static
|
||||
# facts on a path that costs a synchronous round trip to the host under
|
||||
@@ -93,17 +100,18 @@ RUN test -n "$JOBS" || JOBS=$(nproc) && \
|
||||
meson setup builddir \
|
||||
-Dprefix=/usr \
|
||||
-Dbuildtype=release \
|
||||
-Dplatforms=wayland \
|
||||
-Dplatforms=wayland,x11 \
|
||||
-Dgallium-drivers=zink \
|
||||
-Dvulkan-drivers=amd,intel \
|
||||
-Damdgpu-virtio=true \
|
||||
-Dintel-virtio-experimental=true \
|
||||
-Dvideo-codecs=all \
|
||||
-Degl=disabled \
|
||||
-Dglx=disabled \
|
||||
-Degl=enabled \
|
||||
-Dglx=dri \
|
||||
-Dglvnd=enabled \
|
||||
-Dgles1=disabled \
|
||||
-Dgles2=disabled \
|
||||
-Dgbm=disabled \
|
||||
-Dgles2=enabled \
|
||||
-Dgbm=enabled \
|
||||
-Dgallium-va=disabled \
|
||||
-Db_ndebug=true && \
|
||||
mold -run ninja -C builddir -j${JOBS:-$(nproc)} && \
|
||||
|
||||
@@ -110,6 +110,18 @@ RUN pacman -Syu --noconfirm --needed \
|
||||
libsm libtheora xz \
|
||||
&& pacman -Scc --noconfirm
|
||||
|
||||
# Our own patches, one directory per submodule they apply to: patches/proton/
|
||||
# wine/ applies in wine/. Its own layer, after the fetch and the dependencies,
|
||||
# so a patch change costs the build and nothing before it. A patch that no
|
||||
# longer applies to a new tag stops here, before the hours of building.
|
||||
COPY patches/proton /build/patches/proton
|
||||
RUN set -e; cd /build/patches/proton; \
|
||||
for dir in */; do \
|
||||
for p in "${dir}"*.patch; do \
|
||||
echo "applying ${p}"; \
|
||||
patch -d "/build/proton-cachyos/${dir%/}" -p1 --forward < "${p}"; \
|
||||
done; \
|
||||
done
|
||||
COPY scripts/proton-build.sh /build/proton-build.sh
|
||||
RUN GECKO_VER="${GECKO_VER}" MONO_VER="${MONO_VER}" JOBS="${JOBS}" \
|
||||
bash /build/proton-build.sh
|
||||
|
||||
+6
-1
@@ -38,8 +38,13 @@ IMAGE_NAME := ghcr.io/nestrilabs/nestri/base
|
||||
# number in two spellings and an image whose name does not say which Proton is
|
||||
# inside it is worse than no image at all. Changing the tag by hand and
|
||||
# forgetting the version is exactly the mistake this removes.
|
||||
#
|
||||
# `PROTON_PATCHES` counts our own changes on top of that tag (patches/proton/).
|
||||
# Bump it whenever they change, so a patched build never takes the name of the
|
||||
# one before it; reset it to 0 when the tag moves.
|
||||
PROTON_TAG ?= cachyos-11.0-20261005-native
|
||||
PROTON_VERSION := $(PROTON_TAG:cachyos-%-native=%)
|
||||
PROTON_PATCHES ?= 1
|
||||
PROTON_VERSION := $(PROTON_TAG:cachyos-%-native=%)-n$(PROTON_PATCHES)
|
||||
PROTON_IMAGE ?= ghcr.io/nestrilabs/proton-cachyos-native-wow64
|
||||
PROTON_REF := $(PROTON_IMAGE):$(PROTON_VERSION)
|
||||
OUTPUT_DIR := output
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
--- a/dlls/ntdll/unix/env.c
|
||||
+++ b/dlls/ntdll/unix/env.c
|
||||
@@ -286,6 +286,11 @@
|
||||
|
||||
if (!(ctype = setlocale( LC_CTYPE, "" ))) name = "UTF-8";
|
||||
else if (!(name = nl_langinfo( CODESET ))) return;
|
||||
+ /* The C and POSIX locales name plain ASCII, in which a non-ASCII file
|
||||
+ * name cannot be created at all. Steam starts games with LC_ALL=C, so
|
||||
+ * under it every such name failed; ASCII is a subset of UTF-8, so
|
||||
+ * reading it as UTF-8 changes nothing that worked before. */
|
||||
+ if (!strcmp( name, "ANSI_X3.4-1968" )) name = "UTF-8";
|
||||
TRACE_(nls)( "Unix LC_CTYPE %s, using %s codeset\n", debugstr_a(ctype), debugstr_a(name) );
|
||||
|
||||
/* remove punctuation characters from charset name */
|
||||
Reference in New Issue
Block a user