Skip to content

feat(graphics): system-EGL OpenGL backend with NVIDIA performance tuning and uncapped vsync - #85

Open
RowerStudios wants to merge 2 commits into
komaruworld:mainfrom
RowerStudios:feat/nvidia-opengl-backend-perf
Open

feat(graphics): system-EGL OpenGL backend with NVIDIA performance tuning and uncapped vsync#85
RowerStudios wants to merge 2 commits into
komaruworld:mainfrom
RowerStudios:feat/nvidia-opengl-backend-perf

Conversation

@RowerStudios

@RowerStudios RowerStudios commented Aug 27, 2026

Copy link
Copy Markdown

Summary

Add a system-EGL (OpenGL) backend that routes the guest's libEGL/libGLESv2 directly at the host GL stack, plus NVIDIA-specific tuning to remove periodic lag spikes and unlock the frame rate.

  • system-EGL backend: system_egl_bridge / system_egl_probe load the host libEGL + libGLESv2 and expose them as synthetic linker symbols; selectable via the backend policy.
  • Uncapped vsync: egl_swap_interval_shim (LD_PRELOAD) forces eglSwapInterval(0) when graphics.vsync=off, defeating the EGL_MIN_SWAP_INTERVAL=1 clamp.
  • NVIDIA tuning (src/main.cc): __GL_YIELD=USLEEP, __GL_THREADED_OPTIMIZATIONS, __GL_VRR_ALLOWED, shader disk cache, GPUPowerMizer pin, AllowFlipping, config-driven __GL_SYNC_TO_VBLANK/SyncToVBlank, CPU performance governor, renice, persistence mode.
  • Adaptive swap interval (window.cc): SDL_GL_SetSwapInterval(-1) -> 1 honoring MOCKTAIL_VSYNC.
  • Config: graphics.gles_version + graphics.vsync mapped to env vars.
  • launch.sh: helper that preloads the vsync shim and applies driver tuning.

Test plan

  • system_egl_probe_test passes (4 tests).
  • Builds clean; runs Roblox via the system-EGL backend with uncapped FPS when graphics.vsync=off.

Notes

Shader cache confirmed filling (~65 MB in ~/.nv/GLCache on driver 390.x). TextureFilteringQuality=0 trades some sharpness for speed (opt-out MOCKTAIL_DISABLE_NVIDIA_TEXTURE_PERF=1).

Running it now

The normal launch is broken at the moment because login/updater isn't wired up yet (that's being handled by other contributors), so this bypasses it and runs a cached payload directly:

cd ~/mocktail
MOCKTAIL_DEBUG_SHOW_WINDOW_BEFORE_FRAME=1 MOCKTAIL_WINDOW_TRACE=1 \
ROBLOX_LIB_PATH=~/.local/share/mocktail/payloads/2908-63c5109637b7d7b2bdb8ed8f858023ff5ef49326/libroblox.so \
MOCKTAIL_ASSET_PATH=~/.local/share/mocktail/payloads/2908-63c5109637b7d7b2bdb8ed8f858023ff5ef49326/assets/content \
~/mocktail/run

Notes:

  • The window starts hidden until the first presented frame, hence MOCKTAIL_DEBUG_SHOW_WINDOW_BEFORE_FRAME=1 to force it visible.
  • ./build/mocktail falsely reports "already running" (stock-main single-instance lock bug); run a copy of the binary from a different path (~/mocktail/run) to dodge it.

Known issue: the window doesn't show the game yet — the first frame never presents, so it exits with "without a real presented frame". That's the next thing to chase.

@RowerStudios

Copy link
Copy Markdown
Author
image

Verified result here with full OpenGL ES 3.2 working on nvidia 390 driver. with optimizations that generally improve fps on older nvidia cards

@RowerStudios

Copy link
Copy Markdown
Author

Reminder :
launch.sh applies nvidia specific export variables. and it entirely skips the testing for apk.
so new users must not use launch.sh

@glook9001

Copy link
Copy Markdown
Contributor

@RowerStudios @RobertFlexx I am looking forward to add this into my fork! <3

@RowerStudios

Copy link
Copy Markdown
Author

I still need to fix some bugs and errors on libroblox.so .. so yea it will be done in 4 or 5 hours

@glook9001

Copy link
Copy Markdown
Contributor

@RowerStudios!

After extensive profiling and architectural review against the current `main` branch (which includes the runtime, SMT core affinity, and Vulkan WSI optimizations from #73), our recommendation is that **Mocktail should focus      

strictly on Direct Vulkan and drop attempts to maintain a complex host OpenGL/EGL bridge**.

Modern Linux graphics drivers (both Mesa and NVIDIA) are already smart, mature, and natively provide Vulkan across almost all legacy hardware. Here is the detailed breakdown:                                                       
                                                                                                                                                                                                                                     
---                                                                                                                                                                                                                                  

### 1. Modern Linux Graphics Stack Already Handles Legacy Hardware via Vulkan
There is no longer a need to maintain custom OpenGL bridges or `LD_PRELOAD` shims on Linux because **Mesa and NVIDIA already provide first-class Vulkan backports**:
* **NVIDIA 390.x / 470.x Drivers (Kepler / Maxwell)**: The proprietary 390.x/470.x drivers natively provide hardware Vulkan (`nvidia_icd.json`). Additionally, Mesa's open-source **NVK** (`nouveau_icd.json`) provides Vulkan 1.3.  
* **Older Intel (2012–2014 Ivy Bridge / Haswell)**: Mesa maintains **`hasvk`** (`intel_hasvk_icd.json`), bringing full Vulkan 1.3 to legacy Intel iGPUs.
* **Older AMD (2012+ GCN 1.0 / HD 7000+)**: Mesa **`radv`** (`radeon_icd.json`) provides Vulkan 1.3 with full SPIR-V support.
* **CPUs Without a GPU**: Mesa **`lavapipe`** (`lvp_icd.json`) provides a CPU-based software Vulkan 1.3 rasterizer.

---

### 2. Upstream Roblox Android Has Deprecated the OpenGL ES Shader Pack
Roblox's Android build is fundamentally engineered around Vulkan:
* **Vulkan Shader Pack (`shaders_vulkan_mobile.pack`)**: **14.0 MB** (Complete modern pipeline: PBR materials, SurfaceAppearance, Future-is-Bright Phase 3 lighting, Glass reflections, Detiled terrain).
* **OpenGL ES 3 Pack (`shaders_glsles3.pack`)**: **3.8 MB** (Missing ~70% of modern shader permutations).

In OpenGL mode, modern experiences spam hundreds of `Error: shader DefaultUnified...VS80100001 is not available` errors. Because Roblox disabled dynamic runtime shader compilation for GLES3 on mobile builds to avoid stutter,     

these missing shaders cannot be generated on the fly—causing materials to render black/invisible and dropping framerates down to performanceControlCallback (level:0).

---

### 3. Display Servers & ABI Window Compatibility
* **The `eglCreateWindowSurface 3003` Blocker**: In `SystemEglBridge`, passing raw Android `ANativeWindow*` pointers directly into host Linux Mesa fails immediately with `EGL_BAD_ALLOC (0x3003)` because Mesa expects a Wayland    

wl_egl_window* or X11 Window ID.
* Native Wayland / X11 WSI: Mocktail already auto-detects the display topology (WAYLAND_DISPLAY, DISPLAY) and dispatches to the correct Vulkan WSI extension (VK_KHR_wayland_surface vs VK_KHR_xlib_surface) natively
without needing launch.sh wrappers or forcing SDL_VIDEODRIVER=x11.

---

### 4. CPU Latency on Older Processors (i7-2600 Class)
* **`/sys/.../scaling_governor`**: Writing to sysfs requires `root:root` permissions, so `fopen()` fails silently (`EACCES`) for regular users.
* **How #73 Solved This Cleanly**: PR #73 added **SMT Physical Core Topology Affinity** via standard `pthread_setaffinity_np` (pinning hot Render/Vulkan threads to Core 0 and background worker/audio/physics jobs to Cores 1..N).  

This eliminates L1/L2 cache thrashing and thread migration jitter without requiring root access. For dynamic CPU frequency scaling, Mocktail natively communicates with gamemoded via D-Bus.

---

### Recommendation
Maintaining a custom system-EGL bridge with `execv` re-exec loops and `system("nvidia-settings")` subshell forks adds significant technical debt while hitting the upstream GLES3 shader pack limitation.

However, several of the clean environment tuning options in this PR are great and could be cherry-picked into `main` as a clean standalone PR:
* `SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR=1`
* `__GL_VRR_ALLOWED=1`
* `__GL_SHADER_DISK_CACHE_SIZE=2147483648`
* `__GL_YIELD=USLEEP`
* `graphics.vsync` config option & adaptive vsync (`SDL_GL_SetSwapInterval(-1)`)

We recommend closing the `system-EGL` bridge portion of this PR and keeping Mocktail focused on its modern, high-performance Direct Vulkan architecture.

@glook9001

Copy link
Copy Markdown
Contributor

most of the issues you may face I am pretty sure already dealt with pr #73
just gh pr checkout 73

or
git fetch origin pull/73/head:pr-73 git checkout pr-73

and then make run or make build

@glook9001

Copy link
Copy Markdown
Contributor
* **`/sys/.../scaling_governor`**: Writing to sysfs requires `root:root` permissions, so `fopen()` fails silently (`EACCES`) for regular users.

**this shit will be reject within flatpak too, so flatpak bros will suffer **
which also doesnt really matter because we solve it by eliminating deadlocks/mutex/sync/bottlenecks (and more shared pointers in jni [which I still debate with myself how should be dealt with] )

image

@komaruworld

Copy link
Copy Markdown
Owner

@RowerStudios

blockers i found

  1. Both EGL shims are built but neither one is installed. I checked the installed tree and both .so files are absent. The code also searches beside /proc/self/exe, while the normal installed binary and runtime libraries live in different directories. This makes ./build/mocktail behave differently from the installed package.

  2. The NVIDIA re-exec uses the wrong arguments. Mocktail first builds a safe copy of the browser launch request, then ScrubCommandLineLaunchArguments wipes the sensitive value from argv. Later the PR calls execv(self_exe, argv) instead of using the safe copy. On NVIDIA with the preload enabled, a website launch can restart with an empty launch argument.

  3. Backend selection is tied to MOCKTAIL_DISABLE_AUTO_ANGLE_FALLBACK. system, gles and opengl all parse to GraphicsBackend::kSystem, but the new bridge is enabled only for gles and opengl. So aliases with the same parsed backend produce different behavior. Backend selection needs to use the enum, while the fallback flag controls fallback only.

  4. The new SystemEglBridge bypasses Mocktail's existing Bionic EGL adapter. SDL already owns the real EGL context and surface, and the adapter exposes those same handles to Roblox. The new bridge gives Roblox raw host functions, so it can create another context and surface for the same window. Raw eglSwapBuffers also skips Mocktail's first-frame counter, window reveal, present observers and resize readiness. launch.sh forces the debug window to appear early, which can hide this during testing.

  5. ProbeSystemEgl() checks an 8 by 8 surfaceless pbuffer. It never tests the SDL X11 or Wayland window used by Roblox. Production only warns when this probe fails and still loads the bridge. The test for SelectGraphicsBackend() passes, but this runtime path does not use that result. The bridge and probe can also load different EGL libraries when overrides are set.

  6. NVIDIA tuning runs before config is loaded and does not check the selected backend. Even Direct Vulkan launches get OpenGL tuning. The code changes CPU governors, process priority, NVIDIA desktop settings, PowerMizer and persistence mode. Some calls fail for normal users or inside Flatpak, while other changes survive outside Mocktail and are never restored.

launch.sh should leave this PR too. It disables the update check, hardcodes the payload paths, loads a shim from build/ and enables the debug window. The roblox_payload.json update is unrelated and marks the new payload unverified, so that belongs in its own update PR.

what is worth keeping

The useful parts are graphics.gles_version, the graphics.vsync handling, adaptive swap interval and a few process-local NVIDIA environment hints. They need a much smaller implementation.

SDL should remain the owner of the real system EGL context and surface. Set the requested GLES version with SDL attributes before context creation, then keep the existing Bionic adapter between Roblox and the host. Apply the resolved swap interval after the SDL context becomes current. This removes both preload shims and the whole re-exec path.

NVIDIA hints can live in a small policy module that runs after config is loaded and only for system OpenGL. It should respect user values and avoid nvidia-settings, nvidia-smi, sysfs writes and automatic renicing.

I would replace this PR with four smaller ones:

  1. backend aliases, typed GLES and vsync config, plus config tests;
  2. system OpenGL through SDL and the existing Bionic adapter, tested on X11 and Wayland;
  3. presentation policy without preload or re-exec;
  4. optional NVIDIA process hints backed by measurements.

The payload metadata update should stay separate. Before the OpenGL PR is merged, CI needs to test the installed package, browser launch with vsync: off, first-frame visibility, resize after present, and a Direct Vulkan regression run.

@RowerStudios

Copy link
Copy Markdown
Author

Alright @komaruworld and @glook9001
Thank you for the suggestions.. im implementing them rightaway

@RowerStudios
RowerStudios force-pushed the feat/nvidia-opengl-backend-perf branch 2 times, most recently from b8b88f1 to c2798c4 Compare August 29, 2026 05:53
@RowerStudios

RowerStudios commented Aug 29, 2026

Copy link
Copy Markdown
Author

The binary works when i (my llm did some stuff)
for testing, the binary runs via this bypass (points at a cached payload, dodges the lock + updater hang):

MOCKTAIL_DEBUG_SHOW_WINDOW_BEFORE_FRAME=1
ROBLOX_LIB_PATH=~/.local/share/mocktail/payloads/2908-63c5109637b7d7b2bdb8ed8f858023ff5ef49326/libroblox.so
MOCKTAIL_ASSET_PATH=~/.local/share/mocktail/payloads/2908-63c5109637b7d7b2bdb8ed8f858023ff5ef49326/assets/content
./run

To run the built binary.

Because i couldnt get past the built in webview login prompt and there are some issues with mocktail showing that its already running but its not.
and i suspect src/runtime/single_instance_lock.cc.

im not that expert on to those things but yea hopefully someone finds how to fix it!

Because i couldnt get past the built in webview login prompt and there are some issues with mocktail showing that its already running but its not.

im not that expert on to those things but yea hopefully someone finds how to fix it!

@komaruworld @glook9001 @RobertFlexx
by any chance if yall can help me with this login system ill be thankfull!

…ec and driver-side NVIDIA tuning

Per review of PR komaruworld#85:
- Remove the EGL version/vsync preload shims and the NVIDIA re-exec path; the
  shims were never installed and the re-exec passed a wiped argv.
- Remove the separate SystemEglBridge; SDL already owns the EGL context and
  surface and the existing Bionic adapter bridges Roblox's libEGL/libGLESv2 to
  the host. The requested GLES version now comes from graphics.gles_version via
  SDL_GL_SetAttribute before context creation.
- Fix backend selection to use the GraphicsBackend enum; the fallback flag now
  only controls ANGLE fallback.
- Reduce NVIDIA tuning to process-local environment hints applied after config
  load and only for the system OpenGL backend. Drop nvidia-settings,
  nvidia-smi, sysfs governor writes and automatic renicing.
- Drop launch.sh and the unrelated roblox_payload.json metadata change.
@RowerStudios
RowerStudios force-pushed the feat/nvidia-opengl-backend-perf branch from c2798c4 to e951be4 Compare August 29, 2026 06:23
Replace the 8x8 surfaceless-pbuffer probe (which could pass while the
on-screen path failed and resolved a separate EGL library) with a real
hidden SDL window + OpenGL ES context created through the same host EGL
driver the runtime uses. Classify acceleration via GL_RENDERER/GL_VENDOR.

- SystemEglProbeOptions now carries gles_major/gles_minor instead of
  library paths.
- Fix the probe test (it referenced the removed SystemEglBridge) and skip
  when no display is available.
- Link SDL3 into the graphics foundation and the probe test target.
@RowerStudios

Copy link
Copy Markdown
Author

@komaruworld i have done some fixes and im not able to test in wayland cuz wayland is not supported on my gpu

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.

5 participants