Skip to content

Fix macOS build and run issues + input/simulation deadlock#1812

Open
lugt wants to merge 1 commit into
SFTtech:masterfrom
lugt:fix/macos-build-and-run-plus-deadlock
Open

Fix macOS build and run issues + input/simulation deadlock#1812
lugt wants to merge 1 commit into
SFTtech:masterfrom
lugt:fix/macos-build-and-run-plus-deadlock

Conversation

@lugt

@lugt lugt commented Jul 17, 2026

Copy link
Copy Markdown

Continuation of #1777 (Fix macos build and run issues by @Skosulor), rebased onto current master with the review feedback addressed and an additional deadlock fix that the parallel threading exposes on macOS.

What this does

macOS build/run fixes (from #1777)

  1. Run the presenter on the main thread. QGuiApplication must be created and operated on the main thread. On macOS 26 (Tahoe) the platform plugin asserts in -[NSMenu _setMenuName:] ("API misuse: setting the main menu on a non-main thread") otherwise. The simulation is moved to a worker thread instead.
  2. Remove the #ifndef __APPLE__ guard that stopped Qt events from being processed on macOS (gui_application_impl.cpp).
  3. Fix shaders that crashed glLinkProgram on macOS. The macOS OpenGL driver crashes (SIGSEGV in glpLLVMCGSwitchStatement during glLinkProgram) when compiling a switch statement in world2d.frag.glsl — switched to if/else. Also upgraded the GUI shaders from GLSL 1.2 (#version 120, attribute/varying) to 3.3 core (in/out, layout(location=)), and renamed the texture uniform to tex to avoid the GLSL reserved keyword.

Deadlock fix (new, exposed by parallel sim/presenter)

Once the simulation runs in a worker thread and the presenter on the main thread, mouse interaction deadlocks within a few seconds:

  • Main thread (presenter): Controller::process holds Controller::mutex, then calls bind.transform()EventLoop::create_event (takes EventLoop::mutex). Lock order: Controller → EventLoop.
  • Worker thread (simulation): EventLoop::reach_time holds EventLoop::mutex, executes DragSelectHandler::invokeselect_cbController::set_selected (takes Controller::mutex). Lock order: EventLoop → Controller.

Classic AB-BA deadlock (confirmed via lldb attach + thread backtrace all, both threads in __psynch_mutexwait).

Fix: narrow Controller::process's lock scope to only protect the outqueue operations; bind.transform() now runs without holding Controller::mutex. Both threads then take the locks in the order EventLoop → Controller, eliminating the cycle. The bindings' callbacks (get_controlled, get_selected, reset_drag_select, …) already take Controller::mutex themselves and are safe to call unlocked because it is a recursive_mutex.

This is the same class of issue @Skosulor reported in #1777 (mutex lock failed: Invalid argument), just triggered through the drag-select path.

Review feedback (@heinezen) addressed

  • Engine: thread functions (time loop, simulation) extracted into run_time_loop() / run_simulation(); all thread spawning moved into Engine::loop(); comments explain why the main thread runs the presenter in FULL mode (Qt requirement) and the simulation in headless mode. Thread-count log is now printed after spawning (so the number is correct).
  • window_settings moved from Engine into Presenter (stored as a member, passed via the Presenter constructor); Presenter::run() and init_graphics() no longer take it as a parameter.
  • gui.cpp: removed the static constexpr for the texture uniform name; the name is passed inline (now \"tex\").
  • maptexture.frag.glsl: uniform renamed texture_tex.

Two suggestions intentionally not applied — would like your input

  1. identity.vert.glsl: kept gl_Position = vertex_position;. In GLSL 330 core, gl_Position does not automatically contain the current vertex position (it's a write-only output with undefined default); removing it would collapse the GUI quad to the origin (0,0,0,1). This may be a confusion with compatibility-profile gl_Vertex. Could you double-check @heinezen?
  2. world2d.frag.glsl / demo_2_obj / demo_4_obj: kept if/else instead of switch, with a comment explaining the macOS driver crash (glpLLVMCGSwitchStatement SIGSEGV during glLinkProgram). This is a driver bug, not a shader-authoring issue — confirmed by the crash stack. A switch reliably crashes on macOS even though it's valid GLSL.

Verification

Built and run on macOS 26.3 (Apple Silicon), Homebrew Qt 6.11.1 + LLVM clang 22.1.8, Python 3.14. Tested:

  • Game window opens, terrain renders, all render stages (Skybox/Terrain/World/HUD/Screen) initialize.
  • Mouse interaction (drag-select, click) no longer deadlocks; window closes cleanly with all threads exiting (Draw loop exitedGame simulation stoppedTime loop stoppedpresenter exited).
  • renderer_stresstest 0 runs end-to-end (shaders link, entities spawn, FPS stable), confirming the shader fixes.

Supersedes #1777. Happy to split the deadlock fix into a separate PR if preferred.

🤖 Generated with Claude Code

Fixes the build and run problems on macOS (Tahoe / Qt 6.11 / Homebrew
LLVM clang), based on PR SFTtech#1777 by Skosulor, plus a deadlock fix exposed
by the resulting parallel simulation/presenter threads.

Three categories of macOS fixes (from PR SFTtech#1777):

1. Run the presenter on the main thread. QGuiApplication must be created
   and operated on the main thread; on macOS 26 (Tahoe) the platform
   plugin asserts ("setting the main menu on a non-main thread") in
   -[NSMenu _setMenuName:] otherwise. The simulation is moved to a
   worker thread instead.

2. Remove the guard that stopped Qt events from being processed on
   macOS (gui_application_impl.cpp's processEvents was wrapped in
   #ifndef __APPLE__).

3. Fix shaders that crashed glLinkProgram on macOS. The macOS OpenGL
   driver crashes (SIGSEGV in glpLLVMCGSwitchStatement) when compiling
   a switch statement in world2d.frag.glsl; switched to if/else. Also
   upgraded the GUI shaders from GLSL 1.2 (#version 120, attribute/
   varying) to 3.3 core (#version 330 core, in/out, layout(location=)),
   and renamed the `texture` uniform to `tex` to avoid the GLSL reserved
   keyword.

Deadlock fix (exposed by running simulation and presenter in parallel):

  Controller::process held Controller::mutex while calling
  bind.transform(), which calls back into EventLoop::create_event and
  thus takes EventLoop::mutex. The simulation thread takes the locks in
  the opposite order (EventLoop -> Controller, via
  EventLoop::reach_time -> DragSelectHandler -> Controller::set_selected),
  producing an AB-BA deadlock a few seconds into mouse interaction.
  Fixed by narrowing Controller::process's lock scope to only protect
  the outqueue operations; bind.transform() now runs without holding
  Controller::mutex, so both threads take the locks in the order
  EventLoop -> Controller.

Review feedback (heinezen) addressed:

- Engine: thread functions (time loop, simulation) extracted into
  run_time_loop()/run_simulation(); all thread spawning moved into
  Engine::loop(); comments explain why the main thread runs the
  presenter in FULL mode (Qt requirement) and the simulation in
  headless mode. The thread-count log is now printed after spawning.
- window_settings moved from Engine into Presenter (stored as a member,
  passed via the Presenter constructor); Presenter::run() and
  init_graphics() no longer take it as a parameter.
- gui.cpp: removed the static constexpr for the texture uniform name;
  the uniform name is passed inline (now "tex").
- maptexture.frag.glsl: uniform renamed texture_ -> tex.

Two review suggestions intentionally not applied, with rationale to be
discussed in the PR:

- identity.vert.glsl: kept `gl_Position = vertex_position;`. In GLSL
  330 core, gl_Position does not automatically contain the vertex
  position; removing it would collapse the GUI quad to the origin.
- world2d.frag.glsl: kept if/else instead of switch, with a comment
  explaining the macOS driver crash. Same for the demo_2/4_obj shaders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lugt

lugt commented Jul 17, 2026

Copy link
Copy Markdown
Author

@heinezen I've just continued the work left by #1777, and done some fix so that it could actually build on macOS 26 Tahoe, could you help to review and merge this, and I am happy to further change the code if needed.

lugt added a commit to lugt/openage that referenced this pull request Jul 18, 2026
The test GUI (assets/test/qml/main.qml) imports yay.sfttech.openage as
OA, but that module is never registered in libopenage (only
yay.sfttech.livereload is) and OA is not referenced anywhere in the file.
On macOS, once app.processEvents() runs (PR SFTtech#1812), this failed import
aborts QML loading, which makes the presenter draw loop exit immediately
and the game never reaches a playable state. Drop the import so the GUI
loads.

Workaround for the upstream "TODO: Do not use test GUI" (presenter.cpp);
the real GUI is unfinished.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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