Skip to content

fix(troika-three-utils): avoid inout params in vertex transform so text renders in Safari/ANGLE Metal - #392

Merged
lojjic merged 1 commit into
protectwise:mainfrom
rokas-th:fix/safari-angle-inout-globals
Jul 24, 2026
Merged

fix(troika-three-utils): avoid inout params in vertex transform so text renders in Safari/ANGLE Metal#392
lojjic merged 1 commit into
protectwise:mainfrom
rokas-th:fix/safari-angle-inout-globals

Conversation

@rokas-th

Copy link
Copy Markdown

Fixes #390.

Problem

createDerivedMaterial emits the vertex transform as a function taking inout parameters, called with the hoisted shader globals:

void troikaVertexTransform${key}(inout vec3 position, inout vec3 normal, inout vec2 uv) {
  ...
}
...
troikaVertexTransform${key}(troika_position_${key}, troika_normal_${key}, troika_uv_${key});

On Safari (macOS, ANGLE Metal backend), ANGLE hoists those globals into a __metal_generic-address-space struct, then wraps each inout argument in an ANGLE_InOut<T> helper whose conversion operator returns thread T&. The MSL compiler rejects binding the __metal_generic global to a thread reference, so every derived material fails to link:

THREE.WebGLProgram: Shader Error 1282 - VALIDATE_STATUS false
Program Info Log: Internal error while linking shader. MSL compilation error:
  error: reference to type 'thread float3' could not bind to an lvalue of type '__metal_generic float3'
      operator thread T &() { return mTemp; }

followed by an endless stream of WebGL: INVALID_OPERATION: useProgram: program not valid. The result is that no troika-three-text renders in Safari. Chrome/Firefox are unaffected because their ANGLE builds use different backends. This is the ANGLE translator defect tracked upstream in WebKit bug 250169 (see also 319958) — the underlying bug is Safari's, but troika can sidestep it entirely.

Fix

Emit the transform as a parameterless function with explicit copy-in / copy-out locals instead of inout parameters. It's semantically identical to the previous version (same local names, same read-then-write-back semantics), but never passes a global by reference, so it compiles cleanly under ANGLE Metal.

Verification

Running this as a patch-package patch against 0.52.4 in production: text renders correctly again in Safari, with visually identical output in Chrome and Firefox.


Reported and fixed while debugging this in our app; PR opened at maintainer's request (per #390) so the fix is attributed correctly.

createDerivedMaterial emitted `troikaVertexTransform` as a function
taking `inout vec3/vec2` params, called with the hoisted shader
globals. Safari's ANGLE Metal backend cannot bind a __metal_generic
global to a `thread T&` reference, so every derived material fails to
link (Shader Error 1282, VALIDATE_STATUS false) and no text renders.
This is the ANGLE translator defect tracked in WebKit bug 250169 /
319958.

Rewrite the transform as a parameterless function with explicit
copy-in/copy-out locals. This is semantically identical to the inout
version, avoids passing globals by reference, and compiles cleanly
under ANGLE Metal. Verified identical output in Chrome/Firefox.

Fixes protectwise#390

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lojjic
lojjic merged commit bb05826 into protectwise:main Jul 24, 2026
5 checks passed
@lojjic

lojjic commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

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.

[troika-three-text] Text shaders fail to link in Safari (ANGLE Metal): globals passed as inout parameters miscompile

2 participants