Skip to content

Upgrade to Wine 11.5 devel with EGL support and fix GPU rendering#334

Draft
pdelagrave wants to merge 1 commit intonetbrain:masterfrom
pdelagrave:wine11-egl-support
Draft

Upgrade to Wine 11.5 devel with EGL support and fix GPU rendering#334
pdelagrave wants to merge 1 commit intonetbrain:masterfrom
pdelagrave:wine11-egl-support

Conversation

@pdelagrave
Copy link
Copy Markdown

@pdelagrave pdelagrave commented Mar 22, 2026

Summary

Wine 11 switches from GLX to EGL as the default OpenGL backend. This PR upgrades the image to Wine 11.5 devel and fixes several issues required to get hardware GPU rendering working correctly under Docker.

Dockerfile

  • Bump Wine to 11.5~trixie-1 devel (EGL default since 10.17, Wayland native since 9.9)
  • Add libegl1:i386, libgl1:i386, libvulkan1:i386 for 32-bit EGL/GL/Vulkan parity with 64-bit
  • Bake 10_nvidia.json into the image to register NVIDIA as a GLVND EGL vendor — the NVIDIA container runtime injects libEGL_nvidia.so.0 but not the vendor registration file, causing the EGL dispatcher to fall back to Mesa and then software rendering (llvmpipe). Harmless when NVIDIA is absent — GLVND silently skips vendors whose library is not found.

entrypoint.sh

  • Fix ownership check depth (maxdepth 0maxdepth 1): mkdir -p $ZWIFT_HOME runs as root before privilege drop and creates .wine owned by root. The previous maxdepth 0 check on /home/user passed (it is owned by user) while missing the root-owned .wine subdirectory, causing Wine to refuse to start with "wine: '/home/user/.wine' is not owned by you".
  • Fix gosu supplementary groups: gosu user:user skips initgroups() and drops all supplementary groups including the DRI render group. Changed to gosu user so all groups from /etc/group are loaded.
  • Register DRI render group by GID before privilege drop using getent group to find or create the group by GID, avoiding silent conflict with Mesa's pre-existing render group (GID 990).

update_zwift.sh

  • Skip WebView2 install: both the bootstrapper and standalone installer require the MicrosoftEdgeUpdate COM service ({cecddd22-2e72-4832-9606-a9b0e5e344b2}), which Wine cannot activate (out-of-process COM server for a Windows service). SilentLaunch bypasses the launcher UI entirely so WebView2 is not needed at runtime.

zwift.sh

  • Add --ipc=host alongside the X11 socket mount so Mesa can attach to X11 shared memory, which is required for DRI2/DRI3 screen initialization.

Test plan

  • First-time install completes without errors
  • Zwift launches and renders with hardware GPU (OpenGL 4.6.0 NVIDIA 570.211.01 on GTX 1070)
  • CPU usage normal (~11%) vs software rendering fallback (~430-590%)
  • Test with AMD GPU (radeonsi path)
  • Test with Wayland (WINE_EXPERIMENTAL_WAYLAND=1)
  • Test with Podman

🤖 Generated with Claude Code

…NVIDIA

Wine 11 switches from GLX to EGL as the default OpenGL backend, and requires
several fixes to render correctly with an NVIDIA GPU under Docker.

**Dockerfile**
- Bump Wine to 11.5~trixie-1 devel (EGL default since 10.17, Wayland since 9.9)
- Add libegl1:i386, libgl1:i386, libvulkan1:i386 for 32-bit EGL/GL/Vulkan parity
- Register NVIDIA as a GLVND EGL vendor by baking 10_nvidia.json into the image
  so the EGL dispatcher loads libEGL_nvidia.so.0 when injected by the NVIDIA
  container runtime (harmless when NVIDIA is absent — GLVND skips missing libs)

**entrypoint.sh**
- Fix ownership check depth: maxdepth 0 → maxdepth 1 so root-owned .wine
  (created by mkdir -p before privilege drop) is detected and chowned
- Fix gosu supplementary groups: gosu user:user skips initgroups() and drops
  all supplementary groups including the DRI render group; use gosu user instead
- Register DRI render group by GID before privilege drop using getent group to
  avoid silent conflict with Mesa's pre-existing render:990 group

**update_zwift.sh**
- Skip WebView2 install: both bootstrapper and standalone require the
  MicrosoftEdgeUpdate COM service which Wine cannot activate; SilentLaunch
  bypasses the launcher UI so WebView2 is not needed at runtime

**zwift.sh**
- Add --ipc=host alongside the X11 socket mount so Mesa can attach to X11
  shared memory (required for DRI2/DRI3 screen initialization)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment on lines +88 to +90
&& mkdir -p /usr/share/glvnd/egl_vendor.d \
&& printf '{"file_format_version":"1.0.0","ICD":{"library_path":"libEGL_nvidia.so.0"}}\n' \
> /usr/share/glvnd/egl_vendor.d/10_nvidia.json
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you need to add the 10_nvidia.json file? It already exists in the container for me (tried with both docker and podman), I'm guessing this is something the nvidia container toolkit does.

user@glenn-main-pc:/$ ls /usr/share/glvnd/egl_vendor.d/
10_nvidia.json  50_mesa.json
user@glenn-main-pc:/$ cat /usr/share/glvnd/egl_vendor.d/10_nvidia.json 
{
    "file_format_version" : "1.0.0",
    "ICD" : {
        "library_path" : "libEGL_nvidia.so.0"
    }
}

Copy link
Copy Markdown
Collaborator

@glennvl glennvl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried with both docker and podman and I still have the same issues as I have in my own fork glennvl#14 if I don't add --ipc=host.

eglgears_x11 still says MESA: error: Failed to attach to x11 shm even with --ipc=host.

docker

  • With docker zwift launches with software rendering if I add --ipc=host to the zwift and build scripts.
  • Adding the container user to the render group does not make a difference.
libEGL warning: egl: failed to create dri2 screen
libEGL warning: failed to open /dev/dri/card1: Permission denied

podman

With podman zwift launches with software rendering if I add --ipc=host to the zwift and build scripts.

libEGL warning: egl: failed to create dri2 screen

Comment on lines +184 to +200
# Add DRI render group to user so gosu preserves GPU access.
# gosu sets supplementary groups from /etc/group, not from Docker's --group-add,
# so we must register the DRI device's GID in the container before dropping privileges.
# Note: do not use groupadd -f (silently no-ops if group name exists with different GID).
if [[ -d /dev/dri ]]; then
dri_gid="$(stat -c '%g' /dev/dri/renderD128 2>/dev/null || stat -c '%g' /dev/dri/card0 2>/dev/null || true)"
if [[ -n ${dri_gid} ]]; then
if ! getent group "${dri_gid}" > /dev/null 2>&1; then
groupadd -g "${dri_gid}" dri_render 2>/dev/null || true
fi
dri_group="$(getent group "${dri_gid}" | cut -d: -f1)"
if [[ -n ${dri_group} ]]; then
usermod -aG "${dri_group}" user 2>/dev/null || true
msgbox info "Added user to ${dri_group} group (gid=${dri_gid}) for DRI access"
fi
fi
fi
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What issue is this trying to fix? Graphics card access already worked?

It does not change anything for me for egl

libEGL warning: egl: failed to create dri2 screen
libEGL warning: egl: failed to create dri2 screen
libEGL warning: egl: failed to create dri2 screen
libEGL warning: egl: failed to create dri2 screen
libEGL warning: failed to open /dev/dri/card1: Permission denied

On my machine (fedora 43), the card and render devices belong to a different group:

$ ls -al /dev/dri
total 0
drwxr-xr-x.  3 root root        100 23 mrt 09:23 .
drwxr-xr-x. 21 root root       4960 23 mrt 09:23 ..
drwxr-xr-x.  2 root root        100 23 mrt 09:23 by-path
crw-rw----+  1 root video  226,   1 23 mrt 09:23 card1
crw-rw-rw-.  1 root render 226, 128 23 mrt 09:23 renderD128

And the card is /dev/dri/card1, so we shouldn't assume it's card0?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With docker, if I add the user to the group from /dev/dri/card1 and launch with gosu user instead of gosu user:user, the permission denied warning goes away. But the other warning remains and it still uses software rendering instead of hardware rendering.

Comment on lines +140 to +142
# WebView2 install skipped: both bootstrapper and standalone require the
# MicrosoftEdgeUpdate COM service which Wine cannot activate. SilentLaunch
# bypasses the launcher UI so WebView2 is not needed at runtime.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be a specific issue with wine 11.5. It still worked with wine 11.3. But if we don't need webview2, we might indeed as well remove it.

Comment on lines +53 to +57
libegl1:i386 \
libgl1 \
libgl1:i386 \
libvulkan1 \
libvulkan1:i386 \
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you add the 32-bit libraries? They're not needed?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants