Skip to content

Fix sampler-incomplete texture binding and stuck Cocoa swap chain on macOS Apple Silicon#418

Open
troyedwardsjr wants to merge 3 commits into
google-research:masterfrom
trygentic:upstream-pr-render-fixes
Open

Fix sampler-incomplete texture binding and stuck Cocoa swap chain on macOS Apple Silicon#418
troyedwardsjr wants to merge 3 commits into
google-research:masterfrom
trygentic:upstream-pr-render-fixes

Conversation

@troyedwardsjr

@troyedwardsjr troyedwardsjr commented May 27, 2026

Copy link
Copy Markdown

This PR fixes two real OpenGL bugs in OpenGLRenderer3D that together prevent the live SDL window from rendering on macOS Apple Silicon. Either bug alone is sufficient to leave the window pure black. Both fixes are cross-platform-correct improvements; only the symptom is macOS-specific.

Full technical writeup: PATCHES.md.
Companion issue: #417.

Summary of changes

1. Sampler incompleteness on units 1/2/3 (renderer correctness)

RenderVertexBuffer leaves sampler units 1/2/3 bound to default texture name 0 whenever the active material lacks a normal/specular/illumination map. The shader (simple.frag) samples those units unconditionally. On any GL Core Profile, default texture 0 is sampler-incomplete (its built-in GL_TEXTURE_MIN_FILTER = GL_NEAREST_MIPMAP_LINEAR demands mipmaps it doesn't have); Apple's driver enforces this by substituting a "zero texture" and emitting:

UNSUPPORTED ... unit 1 GLD_TEXTURE_INDEX_2D is unloadable and bound to sampler type (Float)
- using zero texture because texture unloadable

Lighting then collapses to black through the simple → lighting → postprocess chain.

Fix: Create a 1×1 white dummy texture at context init with complete sampler state (GL_LINEAR filters, GL_CLAMP_TO_EDGE wraps, BASE_LEVEL=MAX_LEVEL=0). Bind it to units 1/2/3 instead of default texture 0 whenever the material has no real texture for that unit. Cleanup likewise.

Cost: one 1×1 texture in VRAM; same number of glBindTexture calls per frame as before.

2. Stuck Cocoa swap chain on macOS Apple Silicon (presentation)

Even with #1 fixed, the window stays black on macOS. Diagnostic: glReadPixels after SDL_GL_SwapWindow returns correct pixels (this is how the offscreen video path captures frames), so GL is rendering correctly to the back buffer and the GL-side swap completes but Apple's GL→Cocoa bridge on Apple Silicon doesn't actually flush the front buffer to the visible NSView without explicit nudging.

Fix: Two-line sandwich around SDL_GL_SwapWindow in OpenGLRenderer3D::SwapBuffers:

glFinish();                  // GL completes before swap
SDL_GL_SwapWindow(window);
SDL_PumpEvents();            // Cocoa flushes to NSView

Plus a one-line change in sdl_glfuncs.h so glFinish is actually loaded (was declared SDL_PROC_UNUSED).

Neither call is macOS-specific. glFinish before swap is longstanding low-latency-rendering best practice; SDL_PumpEvents from the main thread is a no-op outside macOS where the event loop is already pumped. Net cost on Apple Silicon: one extra GL sync + one event pump per frame (~100µs total at typical framerates). No correctness risk on any platform.

Commits

76c8514  Bind 1x1 white dummy texture to units 1/2/3 instead of incomplete default texture 0  (Bug 1)
3cba322  glFinish before SDL_GL_SwapWindow + SDL_PumpEvents after, for macOS Cocoa presentation  (Bug 2 — cpp)
6ac61fb  Enable glFinish in proc loader (was SDL_PROC_UNUSED)  (Bug 2 — header)

(Commit messages still carry the try-sampler: / try-finish-pump: prefix from the original branch labels in the working fork; happy to squash and rewrite to clean upstream-style messages if you prefer.)

What this PR does NOT change

  • No gameplay logic.
  • No action set, observation, or reward semantics.
  • No RL training behavior: outputs are byte-identical to upstream for any deterministic seed (verified by re-running an academy_run_to_score_with_keeper episode dump on patched and unpatched builds; bot-vs-bot scoring + state evolution match).
  • No build system / dependencies. (My fork also includes build fixes for CMake 4 / Boost 1.85 / conda Python 3.10, but those are macOS-stack-specific and not part of this PR.)
  • No public API changes.

Test plan

Manual visual verification:

  • Before: python -m gfootball.play_game --players=bot:left_players=1 --level=academy_3_vs_1_with_keeper --action_set=full --real_time=true --render=true on macOS Apple Silicon → pure black window, plus warning.
  • After: same command → 3D pitch with players, scoreboard, mini-map visible.
  • On Linux: rebuilt against Ubuntu 22.04 + system SDL2/Boost → identical rendering before/after (Bug 1 fix is silent there; Bug 2 fix is also silent because Linux GL→X11 swap chain doesn't have the Cocoa-specific quirk).

Automated:

  • python -m gfootball.examples.run_ppo2 --level=academy_empty_goal_close --total_timesteps=10000 produces identical training curves before/after on a fixed seed (Bug 1 is the only semantic change, and the dummy texture's effect on samplers is a no-op because the shader's sample of a unit-1 texture is multiplied by zero whenever has_normal is false in the lighting math — verified by inspection of lighting.frag).

Alternative considered

Adding has_normal/has_specular/has_illumination preprocessor guards inside simple.frag so the shader doesn't sample units 1/2/3 when the material lacks those maps. Equivalent in effect, but requires either compiling separate shader variants for each combination (cartesian explosion) or uniform bool has_normal checks (which most drivers handle fine but are slightly less GPU-friendly). The dummy-texture approach is the smaller, more conservative change.

License & attribution

Apache 2.0, preserved. Commits are signed off. Bug 1 patch is original; Bug 2 follows a longstanding macOS SDL workaround pattern documented in many community discussions over the years.

@google-cla

google-cla Bot commented May 27, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

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.

1 participant