@geastack/native-webgl-angle 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2687 @@
1
+ #if defined(_WIN32)
2
+ #ifndef NOMINMAX
3
+ #define NOMINMAX
4
+ #endif
5
+ #include <windows.h>
6
+ #else
7
+ #include "gea/apple/native_bridge.h"
8
+
9
+ #import <AppKit/AppKit.h>
10
+ #import <QuartzCore/QuartzCore.h>
11
+ #import <dispatch/dispatch.h>
12
+
13
+ #include <dlfcn.h>
14
+ #endif
15
+ // Typed uniform arguments use the compiler runtime on both native platforms.
16
+ #include "gea_runtime.h"
17
+ #include <algorithm>
18
+ #include <chrono>
19
+ #include <cstdarg>
20
+ #include <cmath>
21
+ #include <cstddef>
22
+ #include <cstdint>
23
+ #include <cstdio>
24
+ #include <cstdlib>
25
+ #include <cstring>
26
+ #include <string>
27
+ #include <vector>
28
+ #include <span>
29
+ #include <unordered_map>
30
+
31
+ static void geaTrapCheck(const char *where);
32
+
33
+ using EGLBoolean = unsigned int;
34
+ using EGLenum = unsigned int;
35
+ using EGLint = int;
36
+ using EGLDisplay = void *;
37
+ using EGLConfig = void *;
38
+ using EGLContext = void *;
39
+ using EGLSurface = void *;
40
+ using EGLNativeDisplayType = void *;
41
+ using EGLNativeWindowType = void *;
42
+
43
+ using GLenum = unsigned int;
44
+ using GLboolean = unsigned char;
45
+ using GLbitfield = unsigned int;
46
+ using GLchar = char;
47
+ using GLint = int;
48
+ using GLsizei = int;
49
+ using GLsizeiptr = std::ptrdiff_t;
50
+ using GLintptr = std::ptrdiff_t;
51
+ using GLuint = unsigned int;
52
+ using GLfloat = float;
53
+
54
+ // Keyboard state for the native host. macOS delivers key events to the first
55
+ // responder; the host view records the pressed state of each hardware key code
56
+ // (0-127 macOS virtual key codes) into this table, and the game reads it through
57
+ // the `gea_three_key_pressed` intrinsic. This is what makes the native build
58
+ // PLAYABLE — the TS game's `window`-based listeners are inert off the web.
59
+ static bool gGeaKeyState[256] = {false};
60
+
61
+ // Pointer state for the native host. The host view tracks the mouse through an
62
+ // NSTrackingArea (mouseMoved: is only delivered with one — hover without a
63
+ // button press produces no events on a plain NSView) and records the latest
64
+ // position in view coordinates with a TOP-LEFT origin (the web's
65
+ // clientX/clientY convention; AppKit's default view space is bottom-left).
66
+ // The game polls it through `gea_three_pointer_state`, mirroring the keyboard's
67
+ // `gea_three_key_pressed` bridge — this is what makes the plane FOLLOW THE
68
+ // MOUSE natively like the original web game.
69
+ static double gGeaPointerX = 0.0; // view coords, points, top-left origin
70
+ static double gGeaPointerY = 0.0;
71
+ static double gGeaPointerW = 0.0; // view size at the last pointer event
72
+ static double gGeaPointerH = 0.0;
73
+ static double gGeaPointerSeq = 0.0; // increments on every pointer event
74
+ static bool gGeaPointerDown = false; // primary button
75
+
76
+ // gea_three_pointer_state(channel): 0=x, 1=y (top-left origin), 2=view width,
77
+ // 3=view height (same units as x/y), 4=move sequence (0 = pointer never
78
+ // seen; changes on every pointer event), 5=primary button (1 = down).
79
+ //
80
+ // GEA_THREE_SYNTH_POINTER="<x0>,<y0>:<x1>,<y1>:<startMs>:<endMs>" — perf/CI
81
+ // hook, the pointer twin of GEA_THREE_SYNTH_KEY: report a pointer sweeping
82
+ // linearly from (x0,y0) to (x1,y1) during [startMs, endMs) after process
83
+ // start, then hold the end position. Coordinates are NORMALIZED [0,1]
84
+ // fractions of the view (top-left origin), reported with width=height=1, so
85
+ // the sweep is resolution-independent. Combine with GEA_THREE_SYNTH_KEY
86
+ // (Space=49) to start the game headlessly and steer the plane without
87
+ // Accessibility permission for real event injection.
88
+ extern "C" double gea_three_pointer_state(double channel) {
89
+ static bool synthParsed = false;
90
+ static bool synthActive = false;
91
+ static bool synthLatched = false;
92
+ static double sx0 = 0.0, sy0 = 0.0, sx1 = 0.0, sy1 = 0.0;
93
+ static long long synthStartMs = 0, synthEndMs = 0;
94
+ static std::chrono::steady_clock::time_point processStart = std::chrono::steady_clock::now();
95
+ if (!synthParsed) {
96
+ synthParsed = true;
97
+ if (const char *spec = std::getenv("GEA_THREE_SYNTH_POINTER")) {
98
+ double x0 = 0.0, y0 = 0.0, x1 = 0.0, y1 = 0.0;
99
+ long long s = 0, e = 0;
100
+ if (std::sscanf(spec, "%lf,%lf:%lf,%lf:%lld:%lld", &x0, &y0, &x1, &y1, &s, &e) == 6 && e > s) {
101
+ sx0 = x0; sy0 = y0; sx1 = x1; sy1 = y1;
102
+ synthStartMs = s;
103
+ synthEndMs = e;
104
+ synthActive = true;
105
+ }
106
+ }
107
+ }
108
+ if (synthActive) {
109
+ const long long elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
110
+ std::chrono::steady_clock::now() - processStart)
111
+ .count();
112
+ if (elapsed >= synthStartMs && (elapsed < synthEndMs || !synthLatched)) {
113
+ const double t = std::min(1.0, static_cast<double>(elapsed - synthStartMs) /
114
+ static_cast<double>(synthEndMs - synthStartMs));
115
+ gGeaPointerX = sx0 + (sx1 - sx0) * t;
116
+ gGeaPointerY = sy0 + (sy1 - sy0) * t;
117
+ gGeaPointerW = 1.0;
118
+ gGeaPointerH = 1.0;
119
+ // Monotonic per-millisecond sequence: a fresh value each frame during
120
+ // the sweep, one final tick to latch the end position.
121
+ gGeaPointerSeq = 1.0 + static_cast<double>(elapsed - synthStartMs);
122
+ if (elapsed >= synthEndMs) synthLatched = true;
123
+ }
124
+ }
125
+ switch (static_cast<int>(channel)) {
126
+ case 0: return gGeaPointerX;
127
+ case 1: return gGeaPointerY;
128
+ case 2: return gGeaPointerW;
129
+ case 3: return gGeaPointerH;
130
+ case 4: return gGeaPointerSeq;
131
+ case 5: return gGeaPointerDown ? 1.0 : 0.0;
132
+ }
133
+ return 0.0;
134
+ }
135
+
136
+ // Forward declaration for geaInstallPointerMonitor (defined after the state
137
+ // struct; it needs gWebGL.hostView).
138
+ static void geaInstallPointerMonitor(void);
139
+
140
+ extern "C" bool gea_three_key_pressed(double code) {
141
+ const int c = static_cast<int>(code);
142
+ if (c < 0 || c >= 256) return false;
143
+ // GEA_THREE_SYNTH_KEY="<vk>:<startMs>:<endMs>" — perf/CI hook: report the
144
+ // key held during [startMs, endMs) after process start. Drives game-state
145
+ // transitions (e.g. Space=49 to leave the splash) in headless runs where
146
+ // no Accessibility permission is available for real event injection.
147
+ static int synthKey = -1;
148
+ static long long synthStartMs = 0, synthEndMs = 0;
149
+ static std::chrono::steady_clock::time_point processStart = std::chrono::steady_clock::now();
150
+ static bool synthParsed = false;
151
+ if (!synthParsed) {
152
+ synthParsed = true;
153
+ if (const char *spec = std::getenv("GEA_THREE_SYNTH_KEY")) {
154
+ long long k = -1, s = 0, e = 0;
155
+ if (std::sscanf(spec, "%lld:%lld:%lld", &k, &s, &e) == 3) {
156
+ synthKey = static_cast<int>(k);
157
+ synthStartMs = s;
158
+ synthEndMs = e;
159
+ }
160
+ }
161
+ }
162
+ if (c == synthKey) {
163
+ const long long elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
164
+ std::chrono::steady_clock::now() - processStart)
165
+ .count();
166
+ if (elapsed >= synthStartMs && elapsed < synthEndMs) return true;
167
+ }
168
+ return gGeaKeyState[c];
169
+ }
170
+
171
+ #if !defined(_WIN32)
172
+ @interface GeaAngleWebGLHostView : NSView
173
+ @end
174
+
175
+ @implementation GeaAngleWebGLHostView
176
+ - (instancetype)initWithFrame:(NSRect)frame {
177
+ self = [super initWithFrame:frame];
178
+ if (self) {
179
+ self.wantsLayer = YES;
180
+ self.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
181
+ }
182
+ return self;
183
+ }
184
+
185
+ // Accept key events (become first responder) so keyDown:/keyUp: fire.
186
+ - (BOOL)acceptsFirstResponder { return YES; }
187
+
188
+ - (void)viewDidMoveToWindow {
189
+ [super viewDidMoveToWindow];
190
+ if (self.window) {
191
+ [self.window makeFirstResponder:self];
192
+ // Mouse-move events (no button held) are only delivered when the window
193
+ // accepts them; the NSTrackingArea below scopes them to this view.
194
+ self.window.acceptsMouseMovedEvents = YES;
195
+ }
196
+ }
197
+
198
+ // Track mouse movement over the view without requiring a button press.
199
+ // NSTrackingInVisibleRect keeps the area glued to the view's visible bounds
200
+ // across resizes (the rect argument is ignored).
201
+ - (void)updateTrackingAreas {
202
+ [super updateTrackingAreas];
203
+ for (NSTrackingArea *area in [self.trackingAreas copy]) [self removeTrackingArea:area];
204
+ [self addTrackingArea:[[NSTrackingArea alloc]
205
+ initWithRect:NSZeroRect
206
+ options:NSTrackingMouseMoved | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect
207
+ owner:self
208
+ userInfo:nil]];
209
+ }
210
+
211
+ // Pointer RECORDING lives in a window-level NSEvent local monitor (see
212
+ // geaInstallPointerMonitor, installed from gea_three_webgl_attach), NOT in
213
+ // per-view mouseMoved:/mouseDown: overrides: AppKit delivers mouse events to
214
+ // the TOPMOST hit-tested view, so any sibling/overlay view above this one
215
+ // would carve pointer dead zones out of the game surface. The local monitor
216
+ // observes every mouse event dispatched to the window regardless of which
217
+ // view hit-tests it. The tracking area above (plus acceptsMouseMovedEvents)
218
+ // is still required — it is what makes AppKit GENERATE mouse-moved events for
219
+ // the window at all; NSView's default mouseMoved: then simply does nothing.
220
+
221
+ - (void)keyDown:(NSEvent *)event {
222
+ unsigned short code = event.keyCode;
223
+ if (code < 256) gGeaKeyState[code] = true;
224
+ // Swallow the event (no super) so AppKit doesn't emit the system "funk" beep
225
+ // for keys with no menu/command binding.
226
+ }
227
+
228
+ - (void)keyUp:(NSEvent *)event {
229
+ unsigned short code = event.keyCode;
230
+ if (code < 256) gGeaKeyState[code] = false;
231
+ }
232
+
233
+ - (void)flagsChanged:(NSEvent *)event {
234
+ // Track modifier keys as their own key codes too (Shift/Ctrl/etc.), pressed
235
+ // while the corresponding mask bit is set.
236
+ unsigned short code = event.keyCode;
237
+ if (code < 256) gGeaKeyState[code] = (event.modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) != 0;
238
+ }
239
+
240
+ - (CALayer *)makeBackingLayer {
241
+ CAMetalLayer *layer = [CAMetalLayer layer];
242
+ layer.opaque = YES;
243
+ layer.backgroundColor = NSColor.blackColor.CGColor;
244
+ layer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
245
+ layer.needsDisplayOnBoundsChange = YES;
246
+ layer.presentsWithTransaction = NO;
247
+ layer.displaySyncEnabled = YES;
248
+ // Block on nextDrawable instead of dropping the frame. With the default
249
+ // allowsNextDrawableTimeout=YES, [layer nextDrawable] returns nil while the
250
+ // layer is still being realized on-screen at startup (and immediately after a
251
+ // surface rebind); ANGLE then presents nothing and the opaque black
252
+ // backgroundColor shows through — an intermittent black window for the first
253
+ // stretch of frames. Waiting for the drawable keeps rendered content on
254
+ // screen from the first frame.
255
+ layer.allowsNextDrawableTimeout = NO;
256
+ layer.maximumDrawableCount = 3;
257
+ return layer;
258
+ }
259
+ @end
260
+ #endif
261
+
262
+ static constexpr EGLDisplay EGL_NO_DISPLAY = nullptr;
263
+ static constexpr EGLSurface EGL_NO_SURFACE = nullptr;
264
+ static constexpr EGLContext EGL_NO_CONTEXT = nullptr;
265
+ static constexpr EGLNativeDisplayType EGL_DEFAULT_DISPLAY = nullptr;
266
+ static constexpr EGLBoolean EGL_FALSE_VALUE = 0;
267
+
268
+ static constexpr EGLint EGL_NONE = 0x3038;
269
+ static constexpr EGLint EGL_WIDTH = 0x3057;
270
+ static constexpr EGLint EGL_HEIGHT = 0x3056;
271
+ static constexpr EGLint EGL_SURFACE_TYPE = 0x3033;
272
+ static constexpr EGLint EGL_WINDOW_BIT = 0x0004;
273
+ static constexpr EGLint EGL_PBUFFER_BIT = 0x0001;
274
+ static constexpr EGLint EGL_RENDERABLE_TYPE = 0x3040;
275
+ static constexpr EGLint EGL_OPENGL_ES2_BIT = 0x0004;
276
+ static constexpr EGLint EGL_OPENGL_ES3_BIT = 0x0040;
277
+ static constexpr EGLint EGL_RED_SIZE = 0x3024;
278
+ static constexpr EGLint EGL_GREEN_SIZE = 0x3023;
279
+ static constexpr EGLint EGL_BLUE_SIZE = 0x3022;
280
+ static constexpr EGLint EGL_ALPHA_SIZE = 0x3021;
281
+ static constexpr EGLint EGL_DEPTH_SIZE = 0x3025;
282
+ static constexpr EGLint EGL_STENCIL_SIZE = 0x3026;
283
+ static constexpr EGLint EGL_CONTEXT_CLIENT_VERSION = 0x3098;
284
+ static constexpr EGLenum EGL_OPENGL_ES_API = 0x30A0;
285
+ static constexpr EGLenum EGL_PLATFORM_ANGLE_ANGLE = 0x3202;
286
+ static constexpr EGLint EGL_PLATFORM_ANGLE_TYPE_ANGLE = 0x3203;
287
+ static constexpr EGLint EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE = 0x3489;
288
+
289
+ static constexpr GLboolean GL_FALSE_VALUE = 0;
290
+ static constexpr GLboolean GL_TRUE_VALUE = 1;
291
+ static constexpr GLenum GL_FLOAT = 0x1406;
292
+ static constexpr GLenum GL_UNSIGNED_BYTE = 0x1401;
293
+ static constexpr GLenum GL_UNSIGNED_SHORT = 0x1403;
294
+ static constexpr GLenum GL_UNSIGNED_INT = 0x1405;
295
+ static constexpr GLenum GL_RGBA_VALUE = 0x1908;
296
+ static constexpr GLenum GL_VIEWPORT_VALUE = 0x0BA2;
297
+ static constexpr GLenum GL_FRAMEBUFFER_BINDING_VALUE = 0x8CA6;
298
+
299
+ using PFNEGLGETPROCADDRESS = void *(*)(const char *);
300
+ using PFNEGLGETPLATFORMDISPLAYEXTPROC = EGLDisplay (*)(EGLenum, void *, const EGLint *);
301
+ using PFNEGLGETERRORPROC = EGLint (*)();
302
+ using PFNEGLINITIALIZEPROC = EGLBoolean (*)(EGLDisplay, EGLint *, EGLint *);
303
+ using PFNEGLBINDAPIPROC = EGLBoolean (*)(EGLenum);
304
+ using PFNEGLCHOOSECONFIGPROC = EGLBoolean (*)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *);
305
+ using PFNEGLCREATEWINDOWSURFACEPROC = EGLSurface (*)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint *);
306
+ using PFNEGLCREATEPBUFFERSURFACEPROC = EGLSurface (*)(EGLDisplay, EGLConfig, const EGLint *);
307
+ using PFNEGLCREATECONTEXTPROC = EGLContext (*)(EGLDisplay, EGLConfig, EGLContext, const EGLint *);
308
+ using PFNEGLDESTROYSURFACEPROC = EGLBoolean (*)(EGLDisplay, EGLSurface);
309
+ using PFNEGLMAKECURRENTPROC = EGLBoolean (*)(EGLDisplay, EGLSurface, EGLSurface, EGLContext);
310
+ using PFNEGLSWAPBUFFERSPROC = EGLBoolean (*)(EGLDisplay, EGLSurface);
311
+ using PFNEGLSWAPINTERVALPROC = EGLBoolean (*)(EGLDisplay, EGLint);
312
+
313
+ using PFNGLACTIVETEXTUREPROC = void (*)(GLenum);
314
+ using PFNGLATTACHSHADERPROC = void (*)(GLuint, GLuint);
315
+ using PFNGLBINDBUFFERPROC = void (*)(GLenum, GLuint);
316
+ using PFNGLBINDFRAMEBUFFERPROC = void (*)(GLenum, GLuint);
317
+ using PFNGLBINDRENDERBUFFERPROC = void (*)(GLenum, GLuint);
318
+ using PFNGLBINDTEXTUREPROC = void (*)(GLenum, GLuint);
319
+ using PFNGLBINDVERTEXARRAYPROC = void (*)(GLuint);
320
+ using PFNGLBLENDEQUATIONPROC = void (*)(GLenum);
321
+ using PFNGLBLENDEQUATIONSEPARATEPROC = void (*)(GLenum, GLenum);
322
+ using PFNGLBLENDFUNCPROC = void (*)(GLenum, GLenum);
323
+ using PFNGLBLENDFUNCSEPARATEPROC = void (*)(GLenum, GLenum, GLenum, GLenum);
324
+ using PFNGLBUFFERDATAPROC = void (*)(GLenum, GLsizeiptr, const void *, GLenum);
325
+ using PFNGLCOPYBUFFERSUBDATAPROC = void (*)(GLenum, GLenum, GLintptr, GLintptr, GLsizeiptr);
326
+ using PFNGLBUFFERSUBDATAPROC = void (*)(GLenum, GLintptr, GLsizeiptr, const void *);
327
+ using PFNGLCHECKFRAMEBUFFERSTATUSPROC = GLenum (*)(GLenum);
328
+ using PFNGLCLEARPROC = void (*)(GLbitfield);
329
+ using PFNGLCLEARCOLORPROC = void (*)(GLfloat, GLfloat, GLfloat, GLfloat);
330
+ using PFNGLCLEARDEPTHFPROC = void (*)(GLfloat);
331
+ using PFNGLCLEARSTENCILPROC = void (*)(GLint);
332
+ using PFNGLCOLORMASKPROC = void (*)(GLboolean, GLboolean, GLboolean, GLboolean);
333
+ using PFNGLCOMPILESHADERPROC = void (*)(GLuint);
334
+ using PFNGLCREATEPROGRAMPROC = GLuint (*)();
335
+ using PFNGLCREATESHADERPROC = GLuint (*)(GLenum);
336
+ using PFNGLCULLFACEPROC = void (*)(GLenum);
337
+ using PFNGLDELETEBUFFERSPROC = void (*)(GLsizei, const GLuint *);
338
+ using PFNGLDELETEFRAMEBUFFERSPROC = void (*)(GLsizei, const GLuint *);
339
+ using PFNGLDELETEPROGRAMPROC = void (*)(GLuint);
340
+ using PFNGLDELETERENDERBUFFERSPROC = void (*)(GLsizei, const GLuint *);
341
+ using PFNGLDELETESHADERPROC = void (*)(GLuint);
342
+ using PFNGLDELETETEXTURESPROC = void (*)(GLsizei, const GLuint *);
343
+ using PFNGLDELETEVERTEXARRAYSPROC = void (*)(GLsizei, const GLuint *);
344
+ using PFNGLDEPTHFUNCPROC = void (*)(GLenum);
345
+ using PFNGLDEPTHMASKPROC = void (*)(GLboolean);
346
+ using PFNGLDISABLEPROC = void (*)(GLenum);
347
+ using PFNGLDISABLEVERTEXATTRIBARRAYPROC = void (*)(GLuint);
348
+ using PFNGLDRAWARRAYSPROC = void (*)(GLenum, GLint, GLsizei);
349
+ using PFNGLDRAWARRAYSINSTANCEDPROC = void (*)(GLenum, GLint, GLsizei, GLsizei);
350
+ using PFNGLDRAWBUFFERSPROC = void (*)(GLsizei, const GLenum *);
351
+ using PFNGLDRAWELEMENTSPROC = void (*)(GLenum, GLsizei, GLenum, const void *);
352
+ using PFNGLDRAWELEMENTSINSTANCEDPROC = void (*)(GLenum, GLsizei, GLenum, const void *, GLsizei);
353
+ using PFNGLENABLEPROC = void (*)(GLenum);
354
+ using PFNGLENABLEVERTEXATTRIBARRAYPROC = void (*)(GLuint);
355
+ using PFNGLFRAMEBUFFERRENDERBUFFERPROC = void (*)(GLenum, GLenum, GLenum, GLuint);
356
+ using PFNGLFRAMEBUFFERTEXTURE2DPROC = void (*)(GLenum, GLenum, GLenum, GLuint, GLint);
357
+ using PFNGLFRONTFACEPROC = void (*)(GLenum);
358
+ using PFNGLGENBUFFERSPROC = void (*)(GLsizei, GLuint *);
359
+ using PFNGLGENFRAMEBUFFERSPROC = void (*)(GLsizei, GLuint *);
360
+ using PFNGLGENRENDERBUFFERSPROC = void (*)(GLsizei, GLuint *);
361
+ using PFNGLGENTEXTURESPROC = void (*)(GLsizei, GLuint *);
362
+ using PFNGLGENVERTEXARRAYSPROC = void (*)(GLsizei, GLuint *);
363
+ using PFNGLGENERATEMIPMAPPROC = void (*)(GLenum);
364
+ using PFNGLGETACTIVEATTRIBPROC = void (*)(GLuint, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLchar *);
365
+ using PFNGLGETACTIVEUNIFORMPROC = void (*)(GLuint, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLchar *);
366
+ using PFNGLGETATTRIBLOCATIONPROC = GLint (*)(GLuint, const GLchar *);
367
+ using PFNGLGETERRORPROC = GLenum (*)();
368
+ using PFNGLGETINTEGERVPROC = void (*)(GLenum, GLint *);
369
+ using PFNGLGETPROGRAMIVPROC = void (*)(GLuint, GLenum, GLint *);
370
+ using PFNGLGETSHADERIVPROC = void (*)(GLuint, GLenum, GLint *);
371
+ using PFNGLGETSHADERINFOLOGPROC = void (*)(GLuint, GLsizei, GLsizei *, GLchar *);
372
+ using PFNGLGETSTRINGPROC = const unsigned char *(*)(GLenum);
373
+ using PFNGLGETUNIFORMLOCATIONPROC = GLint (*)(GLuint, const GLchar *);
374
+ using PFNGLLINEWIDTHPROC = void (*)(GLfloat);
375
+ using PFNGLLINKPROGRAMPROC = void (*)(GLuint);
376
+ using PFNGLPIXELSTOREIPROC = void (*)(GLenum, GLint);
377
+ using PFNGLPOLYGONOFFSETPROC = void (*)(GLfloat, GLfloat);
378
+ using PFNGLREADPIXELSPROC = void (*)(GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, void *);
379
+ using PFNGLRENDERBUFFERSTORAGEPROC = void (*)(GLenum, GLenum, GLsizei, GLsizei);
380
+ using PFNGLSCISSORPROC = void (*)(GLint, GLint, GLsizei, GLsizei);
381
+ using PFNGLSHADERSOURCEPROC = void (*)(GLuint, GLsizei, const GLchar *const *, const GLint *);
382
+ using PFNGLSTENCILFUNCPROC = void (*)(GLenum, GLint, GLuint);
383
+ using PFNGLSTENCILFUNCSEPARATEPROC = void (*)(GLenum, GLenum, GLint, GLuint);
384
+ using PFNGLSTENCILMASKPROC = void (*)(GLuint);
385
+ using PFNGLSTENCILMASKSEPARATEPROC = void (*)(GLenum, GLuint);
386
+ using PFNGLSTENCILOPPROC = void (*)(GLenum, GLenum, GLenum);
387
+ using PFNGLSTENCILOPSEPARATEPROC = void (*)(GLenum, GLenum, GLenum, GLenum);
388
+ using PFNGLTEXIMAGE2DPROC = void (*)(GLenum, GLint, GLint, GLsizei, GLsizei, GLint, GLenum, GLenum, const void *);
389
+ using PFNGLTEXPARAMETERFPROC = void (*)(GLenum, GLenum, GLfloat);
390
+ using PFNGLTEXPARAMETERIPROC = void (*)(GLenum, GLenum, GLint);
391
+ using PFNGLTEXSUBIMAGE2DPROC = void (*)(GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const void *);
392
+ using PFNGLTEXSTORAGE2DPROC = void (*)(GLenum, GLsizei, GLenum, GLsizei, GLsizei);
393
+ using PFNGLUNIFORM1FPROC = void (*)(GLint, GLfloat);
394
+ using PFNGLUNIFORM1FVPROC = void (*)(GLint, GLsizei, const GLfloat *);
395
+ using PFNGLUNIFORM1IPROC = void (*)(GLint, GLint);
396
+ using PFNGLUNIFORM1IVPROC = void (*)(GLint, GLsizei, const GLint *);
397
+ using PFNGLUNIFORM1UIPROC = void (*)(GLint, GLuint);
398
+ using PFNGLUNIFORM1UIVPROC = void (*)(GLint, GLsizei, const GLuint *);
399
+ using PFNGLUNIFORM2FPROC = void (*)(GLint, GLfloat, GLfloat);
400
+ using PFNGLUNIFORM2FVPROC = void (*)(GLint, GLsizei, const GLfloat *);
401
+ using PFNGLUNIFORM2IPROC = void (*)(GLint, GLint, GLint);
402
+ using PFNGLUNIFORM2IVPROC = void (*)(GLint, GLsizei, const GLint *);
403
+ using PFNGLUNIFORM2UIPROC = void (*)(GLint, GLuint, GLuint);
404
+ using PFNGLUNIFORM2UIVPROC = void (*)(GLint, GLsizei, const GLuint *);
405
+ using PFNGLUNIFORM3FPROC = void (*)(GLint, GLfloat, GLfloat, GLfloat);
406
+ using PFNGLUNIFORM3FVPROC = void (*)(GLint, GLsizei, const GLfloat *);
407
+ using PFNGLUNIFORM3IPROC = void (*)(GLint, GLint, GLint, GLint);
408
+ using PFNGLUNIFORM3IVPROC = void (*)(GLint, GLsizei, const GLint *);
409
+ using PFNGLUNIFORM3UIPROC = void (*)(GLint, GLuint, GLuint, GLuint);
410
+ using PFNGLUNIFORM3UIVPROC = void (*)(GLint, GLsizei, const GLuint *);
411
+ using PFNGLUNIFORM4FPROC = void (*)(GLint, GLfloat, GLfloat, GLfloat, GLfloat);
412
+ using PFNGLUNIFORM4FVPROC = void (*)(GLint, GLsizei, const GLfloat *);
413
+ using PFNGLUNIFORM4IPROC = void (*)(GLint, GLint, GLint, GLint, GLint);
414
+ using PFNGLUNIFORM4IVPROC = void (*)(GLint, GLsizei, const GLint *);
415
+ using PFNGLUNIFORM4UIPROC = void (*)(GLint, GLuint, GLuint, GLuint, GLuint);
416
+ using PFNGLUNIFORM4UIVPROC = void (*)(GLint, GLsizei, const GLuint *);
417
+ using PFNGLUNIFORMMATRIX2FVPROC = void (*)(GLint, GLsizei, GLboolean, const GLfloat *);
418
+ using PFNGLUNIFORMMATRIX3FVPROC = void (*)(GLint, GLsizei, GLboolean, const GLfloat *);
419
+ using PFNGLUNIFORMMATRIX4FVPROC = void (*)(GLint, GLsizei, GLboolean, const GLfloat *);
420
+ using PFNGLUSEPROGRAMPROC = void (*)(GLuint);
421
+ using PFNGLVERTEXATTRIBDIVISORPROC = void (*)(GLuint, GLuint);
422
+ using PFNGLVERTEXATTRIBPOINTERPROC = void (*)(GLuint, GLint, GLenum, GLboolean, GLsizei, const void *);
423
+ using PFNGLVIEWPORTPROC = void (*)(GLint, GLint, GLsizei, GLsizei);
424
+
425
+ struct AngleWebGLState {
426
+ void *egl = nullptr;
427
+ void *gles = nullptr;
428
+ EGLDisplay display = EGL_NO_DISPLAY;
429
+ EGLConfig config = nullptr;
430
+ EGLSurface surface = EGL_NO_SURFACE;
431
+ EGLContext context = EGL_NO_CONTEXT;
432
+ PFNEGLGETPROCADDRESS eglGetProcAddress = nullptr;
433
+ PFNEGLGETERRORPROC eglGetError = nullptr;
434
+ PFNEGLCREATEWINDOWSURFACEPROC eglCreateWindowSurface = nullptr;
435
+ PFNEGLCREATEPBUFFERSURFACEPROC eglCreatePbufferSurface = nullptr;
436
+ PFNEGLDESTROYSURFACEPROC eglDestroySurface = nullptr;
437
+ PFNEGLMAKECURRENTPROC eglMakeCurrent = nullptr;
438
+ PFNEGLSWAPBUFFERSPROC eglSwapBuffers = nullptr;
439
+ PFNEGLSWAPINTERVALPROC eglSwapInterval = nullptr;
440
+
441
+ PFNGLACTIVETEXTUREPROC glActiveTexture = nullptr;
442
+ PFNGLATTACHSHADERPROC glAttachShader = nullptr;
443
+ PFNGLBINDBUFFERPROC glBindBuffer = nullptr;
444
+ PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer = nullptr;
445
+ PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer = nullptr;
446
+ PFNGLBINDTEXTUREPROC glBindTexture = nullptr;
447
+ PFNGLBINDVERTEXARRAYPROC glBindVertexArray = nullptr;
448
+ PFNGLBLENDEQUATIONPROC glBlendEquation = nullptr;
449
+ PFNGLBLENDEQUATIONSEPARATEPROC glBlendEquationSeparate = nullptr;
450
+ PFNGLBLENDFUNCPROC glBlendFunc = nullptr;
451
+ PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate = nullptr;
452
+ PFNGLBUFFERDATAPROC glBufferData = nullptr;
453
+ PFNGLBUFFERSUBDATAPROC glBufferSubData = nullptr;
454
+ PFNGLCOPYBUFFERSUBDATAPROC glCopyBufferSubData = nullptr;
455
+ PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus = nullptr;
456
+ PFNGLCLEARPROC glClear = nullptr;
457
+ PFNGLCLEARCOLORPROC glClearColor = nullptr;
458
+ PFNGLCLEARDEPTHFPROC glClearDepthf = nullptr;
459
+ PFNGLCLEARSTENCILPROC glClearStencil = nullptr;
460
+ PFNGLCOLORMASKPROC glColorMask = nullptr;
461
+ PFNGLCOMPILESHADERPROC glCompileShader = nullptr;
462
+ PFNGLCREATEPROGRAMPROC glCreateProgram = nullptr;
463
+ PFNGLCREATESHADERPROC glCreateShader = nullptr;
464
+ PFNGLCULLFACEPROC glCullFace = nullptr;
465
+ PFNGLDELETEBUFFERSPROC glDeleteBuffers = nullptr;
466
+ PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers = nullptr;
467
+ PFNGLDELETEPROGRAMPROC glDeleteProgram = nullptr;
468
+ PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers = nullptr;
469
+ PFNGLDELETESHADERPROC glDeleteShader = nullptr;
470
+ PFNGLDELETETEXTURESPROC glDeleteTextures = nullptr;
471
+ PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays = nullptr;
472
+ PFNGLDEPTHFUNCPROC glDepthFunc = nullptr;
473
+ PFNGLDEPTHMASKPROC glDepthMask = nullptr;
474
+ PFNGLDISABLEPROC glDisable = nullptr;
475
+ PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray = nullptr;
476
+ PFNGLDRAWARRAYSPROC glDrawArrays = nullptr;
477
+ PFNGLDRAWARRAYSINSTANCEDPROC glDrawArraysInstanced = nullptr;
478
+ PFNGLDRAWBUFFERSPROC glDrawBuffers = nullptr;
479
+ PFNGLDRAWELEMENTSPROC glDrawElements = nullptr;
480
+ PFNGLDRAWELEMENTSINSTANCEDPROC glDrawElementsInstanced = nullptr;
481
+ PFNGLENABLEPROC glEnable = nullptr;
482
+ PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = nullptr;
483
+ PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer = nullptr;
484
+ PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D = nullptr;
485
+ PFNGLFRONTFACEPROC glFrontFace = nullptr;
486
+ PFNGLGENBUFFERSPROC glGenBuffers = nullptr;
487
+ PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers = nullptr;
488
+ PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers = nullptr;
489
+ PFNGLGENTEXTURESPROC glGenTextures = nullptr;
490
+ PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = nullptr;
491
+ PFNGLGENERATEMIPMAPPROC glGenerateMipmap = nullptr;
492
+ PFNGLGETACTIVEATTRIBPROC glGetActiveAttrib = nullptr;
493
+ PFNGLGETACTIVEUNIFORMPROC glGetActiveUniform = nullptr;
494
+ PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation = nullptr;
495
+ PFNGLGETERRORPROC glGetError = nullptr;
496
+ PFNGLGETINTEGERVPROC glGetIntegerv = nullptr;
497
+ PFNGLGETPROGRAMIVPROC glGetProgramiv = nullptr;
498
+ PFNGLGETSHADERIVPROC glGetShaderiv = nullptr;
499
+ PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = nullptr;
500
+ PFNGLGETSTRINGPROC glGetString = nullptr;
501
+ PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr;
502
+ PFNGLLINEWIDTHPROC glLineWidth = nullptr;
503
+ PFNGLLINKPROGRAMPROC glLinkProgram = nullptr;
504
+ PFNGLPIXELSTOREIPROC glPixelStorei = nullptr;
505
+ PFNGLPOLYGONOFFSETPROC glPolygonOffset = nullptr;
506
+ PFNGLREADPIXELSPROC glReadPixels = nullptr;
507
+ PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage = nullptr;
508
+ PFNGLSCISSORPROC glScissor = nullptr;
509
+ PFNGLSHADERSOURCEPROC glShaderSource = nullptr;
510
+ PFNGLSTENCILFUNCPROC glStencilFunc = nullptr;
511
+ PFNGLSTENCILFUNCSEPARATEPROC glStencilFuncSeparate = nullptr;
512
+ PFNGLSTENCILMASKPROC glStencilMask = nullptr;
513
+ PFNGLSTENCILMASKSEPARATEPROC glStencilMaskSeparate = nullptr;
514
+ PFNGLSTENCILOPPROC glStencilOp = nullptr;
515
+ PFNGLSTENCILOPSEPARATEPROC glStencilOpSeparate = nullptr;
516
+ PFNGLTEXIMAGE2DPROC glTexImage2D = nullptr;
517
+ PFNGLTEXPARAMETERFPROC glTexParameterf = nullptr;
518
+ PFNGLTEXPARAMETERIPROC glTexParameteri = nullptr;
519
+ PFNGLTEXSUBIMAGE2DPROC glTexSubImage2D = nullptr;
520
+ PFNGLTEXSTORAGE2DPROC glTexStorage2D = nullptr;
521
+ PFNGLUNIFORM1FPROC glUniform1f = nullptr;
522
+ PFNGLUNIFORM1FVPROC glUniform1fv = nullptr;
523
+ PFNGLUNIFORM1IPROC glUniform1i = nullptr;
524
+ PFNGLUNIFORM1IVPROC glUniform1iv = nullptr;
525
+ PFNGLUNIFORM1UIPROC glUniform1ui = nullptr;
526
+ PFNGLUNIFORM1UIVPROC glUniform1uiv = nullptr;
527
+ PFNGLUNIFORM2FPROC glUniform2f = nullptr;
528
+ PFNGLUNIFORM2FVPROC glUniform2fv = nullptr;
529
+ PFNGLUNIFORM2IPROC glUniform2i = nullptr;
530
+ PFNGLUNIFORM2IVPROC glUniform2iv = nullptr;
531
+ PFNGLUNIFORM2UIPROC glUniform2ui = nullptr;
532
+ PFNGLUNIFORM2UIVPROC glUniform2uiv = nullptr;
533
+ PFNGLUNIFORM3FPROC glUniform3f = nullptr;
534
+ PFNGLUNIFORM3FVPROC glUniform3fv = nullptr;
535
+ PFNGLUNIFORM3IPROC glUniform3i = nullptr;
536
+ PFNGLUNIFORM3IVPROC glUniform3iv = nullptr;
537
+ PFNGLUNIFORM3UIPROC glUniform3ui = nullptr;
538
+ PFNGLUNIFORM3UIVPROC glUniform3uiv = nullptr;
539
+ PFNGLUNIFORM4FPROC glUniform4f = nullptr;
540
+ PFNGLUNIFORM4FVPROC glUniform4fv = nullptr;
541
+ PFNGLUNIFORM4IPROC glUniform4i = nullptr;
542
+ PFNGLUNIFORM4IVPROC glUniform4iv = nullptr;
543
+ PFNGLUNIFORM4UIPROC glUniform4ui = nullptr;
544
+ PFNGLUNIFORM4UIVPROC glUniform4uiv = nullptr;
545
+ PFNGLUNIFORMMATRIX2FVPROC glUniformMatrix2fv = nullptr;
546
+ PFNGLUNIFORMMATRIX3FVPROC glUniformMatrix3fv = nullptr;
547
+ PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv = nullptr;
548
+ PFNGLUSEPROGRAMPROC glUseProgram = nullptr;
549
+ PFNGLVERTEXATTRIBDIVISORPROC glVertexAttribDivisor = nullptr;
550
+ PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = nullptr;
551
+ PFNGLVIEWPORTPROC glViewport = nullptr;
552
+
553
+ #if defined(_WIN32)
554
+ void *nativeWindow = nullptr;
555
+ #else
556
+ NSView *hostView = nil;
557
+ CAMetalLayer *metalLayer = nil;
558
+ #endif
559
+ int widthPx = 1;
560
+ int heightPx = 1;
561
+ double devicePixelRatio = 1.0;
562
+ bool ready = false;
563
+ };
564
+
565
+ static AngleWebGLState gWebGL;
566
+ static std::chrono::steady_clock::time_point gProfileSizeSync;
567
+ static bool gProfileSizeSyncPending = false;
568
+ static bool nativeFrameProfileEnabled() {
569
+ static const bool enabled = std::getenv("GEA_NATIVE_FRAME_PROFILE") != nullptr;
570
+ return enabled;
571
+ }
572
+
573
+ // Diagnostic switches are launch settings, like nativeFrameProfileEnabled.
574
+ // Cache them lazily, after UWP has applied its launch configuration: repeated
575
+ // getenv calls acquire the CRT environment lock in per-draw uniform/state paths.
576
+ static bool nativeWebGLTrapEnabled() {
577
+ static const bool enabled = std::getenv("GEA_WEBGL_TRAP") != nullptr;
578
+ return enabled;
579
+ }
580
+ static bool nativeBlendDebugEnabled() {
581
+ static const bool enabled = std::getenv("GEA_WEBGL_BLEND_DEBUG") != nullptr;
582
+ return enabled;
583
+ }
584
+ static bool nativeShadowColorAuditEnabled() {
585
+ static const bool enabled = std::getenv("GEA_WEBGL_SHADOW_COLOR_AUDIT") != nullptr;
586
+ return enabled;
587
+ }
588
+
589
+ static void smokeLog(const char *format, ...) {
590
+ char message[1024] = {};
591
+ va_list args;
592
+ va_start(args, format);
593
+ std::vsnprintf(message, sizeof(message), format, args);
594
+ va_end(args);
595
+
596
+ const char *path = std::getenv("GEA_THREE_ANGLE_SMOKE_LOG");
597
+ #if defined(_WIN32)
598
+ OutputDebugStringA(message);
599
+ OutputDebugStringA("\n");
600
+ if (!path || path[0] == '\0') {
601
+ std::fprintf(stderr, "%s\n", message);
602
+ return;
603
+ }
604
+ #else
605
+ if (!path || path[0] == '\0') path = "/tmp/three-angle-metal-smoke.log";
606
+ #endif
607
+ if (FILE *file = std::fopen(path, "a")) {
608
+ std::fprintf(file, "%s\n", message);
609
+ std::fclose(file);
610
+ }
611
+ std::fprintf(stderr, "%s\n", message);
612
+ }
613
+
614
+ extern "C" void gea_three_angle_smoke_log(std::string message) {
615
+ smokeLog("[three-angle-metal] %s", message.c_str());
616
+ }
617
+
618
+ // Window-level pointer capture: a local NSEvent monitor sees EVERY mouse
619
+ // event dispatched to this app before window/view routing, so pointer
620
+ // tracking cannot be blocked by whatever view AppKit hit-tests topmost
621
+ // (overlay labels etc. would otherwise carve dead zones — per-view
622
+ // mouseMoved: overrides only fire when THAT view wins hit-testing). The
623
+ // handler records the position in the host view's coordinate space with the
624
+ // same top-left-origin flip the per-view path used. Returning the event
625
+ // unmodified keeps normal dispatch (clicks, first responder) intact.
626
+ #if !defined(_WIN32)
627
+ static id gGeaPointerMonitor = nil;
628
+ static void geaInstallPointerMonitor(void) {
629
+ if (gGeaPointerMonitor) return;
630
+ const NSEventMask mask = NSEventMaskMouseMoved | NSEventMaskLeftMouseDragged |
631
+ NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp;
632
+ gGeaPointerMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:mask
633
+ handler:^NSEvent *(NSEvent *event) {
634
+ NSView *host = gWebGL.hostView;
635
+ if (host && host.window && event.window == host.window) {
636
+ if (event.type == NSEventTypeLeftMouseDown) gGeaPointerDown = true;
637
+ else if (event.type == NSEventTypeLeftMouseUp) gGeaPointerDown = false;
638
+ const NSPoint p = [host convertPoint:event.locationInWindow fromView:nil];
639
+ gGeaPointerX = p.x;
640
+ gGeaPointerY = host.bounds.size.height - p.y; // clientY: top-left origin
641
+ gGeaPointerW = host.bounds.size.width;
642
+ gGeaPointerH = host.bounds.size.height;
643
+ gGeaPointerSeq += 1.0;
644
+ }
645
+ return event;
646
+ }];
647
+ smokeLog("[three-angle-metal] pointer monitor installed (window-level)");
648
+ }
649
+
650
+ static void *openFirst(const char *envName, const char *const *paths) {
651
+ if (const char *overridePath = std::getenv(envName)) {
652
+ if (void *handle = dlopen(overridePath, RTLD_NOW | RTLD_LOCAL)) {
653
+ std::fprintf(stderr, "[three-angle-metal] loaded %s from %s\n", envName, overridePath);
654
+ return handle;
655
+ }
656
+ std::fprintf(stderr, "[three-angle-metal] failed to load %s from %s: %s\n", envName, overridePath, dlerror());
657
+ }
658
+ for (int i = 0; paths[i] != nullptr; i++) {
659
+ if (void *handle = dlopen(paths[i], RTLD_NOW | RTLD_LOCAL)) {
660
+ std::fprintf(stderr, "[three-angle-metal] loaded %s\n", paths[i]);
661
+ return handle;
662
+ }
663
+ }
664
+ return nullptr;
665
+ }
666
+
667
+ #endif
668
+
669
+ template <typename Fn>
670
+ static Fn loadSymbol(void *library, const char *name) {
671
+ #if defined(_WIN32)
672
+ return reinterpret_cast<Fn>(GetProcAddress(static_cast<HMODULE>(library), name));
673
+ #else
674
+ return reinterpret_cast<Fn>(dlsym(library, name));
675
+ #endif
676
+ }
677
+
678
+ template <typename Fn>
679
+ static Fn loadGl(const char *name) {
680
+ if (gWebGL.eglGetProcAddress) {
681
+ if (void *symbol = gWebGL.eglGetProcAddress(name)) return reinterpret_cast<Fn>(symbol);
682
+ }
683
+ return loadSymbol<Fn>(gWebGL.gles, name);
684
+ }
685
+
686
+ static EGLint lastEglError() {
687
+ return gWebGL.eglGetError ? gWebGL.eglGetError() : 0;
688
+ }
689
+
690
+ // Optional asynchronous GPU timings. Never wait for a result or insert a
691
+ // glFinish: an unavailable ring slot simply skips that frame's measurement.
692
+ // The single host context owns these query names until context destruction.
693
+ struct GpuFrameProfile {
694
+ void (*gen)(GLsizei, GLuint *) = nullptr;
695
+ void (*begin)(GLenum, GLuint) = nullptr;
696
+ void (*end)(GLenum) = nullptr;
697
+ void (*available)(GLuint, GLenum, GLuint *) = nullptr;
698
+ void (*result)(GLuint, GLenum, std::uint64_t *) = nullptr;
699
+ GLuint queries[8]{};
700
+ bool pending[8]{}, valid[8]{};
701
+ bool initialized = false, enabled = false, active = false;
702
+ unsigned slot = 0, count = 0;
703
+ double total = 0, maximum = 0;
704
+ std::chrono::steady_clock::time_point window = std::chrono::steady_clock::now();
705
+
706
+ void start() {
707
+ if (!nativeFrameProfileEnabled() || !gWebGL.ready || active) return;
708
+ if (!initialized) {
709
+ initialized = true;
710
+ const auto extensions = reinterpret_cast<const char *>(gWebGL.glGetString(0x1F03));
711
+ const std::string list = " " + std::string(extensions ? extensions : "") + " ";
712
+ if (list.find(" GL_EXT_disjoint_timer_query ") != std::string::npos) {
713
+ gen = loadGl<decltype(gen)>("glGenQueriesEXT");
714
+ begin = loadGl<decltype(begin)>("glBeginQueryEXT");
715
+ end = loadGl<decltype(end)>("glEndQueryEXT");
716
+ available = loadGl<decltype(available)>("glGetQueryObjectuivEXT");
717
+ result = loadGl<decltype(result)>("glGetQueryObjectui64vEXT");
718
+ auto bits = loadGl<void (*)(GLenum, GLenum, GLint *)>("glGetQueryivEXT");
719
+ GLint counterBits = 0;
720
+ if (bits) bits(0x88BF, 0x8864, &counterBits);
721
+ enabled = gen && begin && end && available && result && counterBits > 0;
722
+ }
723
+ smokeLog("[gea.gpu-profile] timer_supported=%d", enabled ? 1 : 0);
724
+ if (enabled) gen(8, queries);
725
+ }
726
+ if (!enabled) return;
727
+ GLint disjoint = 0;
728
+ gWebGL.glGetIntegerv(0x8FBB, &disjoint);
729
+ if (disjoint) {
730
+ for (auto &value : valid) value = false;
731
+ total = maximum = 0; count = 0;
732
+ }
733
+ for (unsigned i = 0; i < 8; ++i) {
734
+ if (!pending[i]) continue;
735
+ GLuint ready = 0;
736
+ available(queries[i], 0x8867, &ready);
737
+ if (!ready) continue;
738
+ if (valid[i]) {
739
+ std::uint64_t ns = 0;
740
+ result(queries[i], 0x8866, &ns);
741
+ const double ms = static_cast<double>(ns) / 1000000.0;
742
+ total += ms; maximum = std::max(maximum, ms); ++count;
743
+ }
744
+ pending[i] = false;
745
+ }
746
+ const auto now = std::chrono::steady_clock::now();
747
+ if (std::chrono::duration<double>(now - window).count() >= 1.0) {
748
+ if (count) smokeLog("[gea.gpu-profile] mean_ms=%.3f max_ms=%.3f samples=%u", total / count, maximum, count);
749
+ total = maximum = 0; count = 0; window = now;
750
+ }
751
+ if (!pending[slot]) {
752
+ begin(0x88BF, queries[slot]);
753
+ active = true;
754
+ }
755
+ }
756
+ void finish() {
757
+ if (!active) return;
758
+ end(0x88BF);
759
+ pending[slot] = valid[slot] = true;
760
+ slot = (slot + 1) % 8;
761
+ active = false;
762
+ }
763
+ };
764
+ static GpuFrameProfile gGpuFrameProfile;
765
+
766
+ static void logEglFailure(const char *step) {
767
+ std::fprintf(stderr, "[three-angle-metal] %s failed, EGL error 0x%04x\n", step, lastEglError());
768
+ }
769
+
770
+ #if !defined(_WIN32)
771
+ static CGFloat backingScaleForHostView(NSView *nativeView) {
772
+ CGFloat scale = nativeView.window ? nativeView.window.backingScaleFactor : 0.0;
773
+ if (scale <= 0.0 && nativeView.window.screen) scale = nativeView.window.screen.backingScaleFactor;
774
+ if (scale <= 0.0 && NSScreen.mainScreen) scale = NSScreen.mainScreen.backingScaleFactor;
775
+ if (scale <= 0.0) scale = static_cast<CGFloat>(gWebGL.devicePixelRatio > 0.0 ? gWebGL.devicePixelRatio : 1.0);
776
+ return std::max<CGFloat>(1.0, scale);
777
+ }
778
+
779
+ static NSRect resolvedHostBounds(NSView *nativeView) {
780
+ if (NSView *contentView = nativeView.window.contentView) [contentView layoutSubtreeIfNeeded];
781
+ NSView *containerView = nativeView.superview;
782
+ if (containerView) [containerView layoutSubtreeIfNeeded];
783
+ NSRect targetBounds = containerView ? containerView.bounds : nativeView.bounds;
784
+ if ((targetBounds.size.width <= 0.0 || targetBounds.size.height <= 0.0) && nativeView.window.contentView) {
785
+ targetBounds = nativeView.window.contentView.bounds;
786
+ }
787
+ return targetBounds;
788
+ }
789
+
790
+ static double syncHostViewSize(double fallbackAspect) {
791
+ NSView *nativeView = gWebGL.hostView;
792
+ if (!nativeView) return fallbackAspect > 0.0 ? fallbackAspect : 1.0;
793
+ NSRect targetBounds = resolvedHostBounds(nativeView);
794
+ if (targetBounds.size.width <= 0.0 || targetBounds.size.height <= 0.0) {
795
+ return fallbackAspect > 0.0 ? fallbackAspect : 1.0;
796
+ }
797
+
798
+ [CATransaction begin];
799
+ [CATransaction setDisableActions:YES];
800
+ nativeView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
801
+ NSRect targetFrame = NSMakeRect(0.0, 0.0, targetBounds.size.width, targetBounds.size.height);
802
+ nativeView.frame = targetFrame;
803
+ nativeView.bounds = targetFrame;
804
+ [nativeView layoutSubtreeIfNeeded];
805
+
806
+ CGFloat scale = backingScaleForHostView(nativeView);
807
+ gWebGL.widthPx = std::max(1, static_cast<int>(std::round(targetBounds.size.width * scale)));
808
+ gWebGL.heightPx = std::max(1, static_cast<int>(std::round(targetBounds.size.height * scale)));
809
+ gWebGL.devicePixelRatio = static_cast<double>(scale);
810
+
811
+ if ([nativeView.layer isKindOfClass:CAMetalLayer.class]) {
812
+ CAMetalLayer *metalLayer = (CAMetalLayer *)nativeView.layer;
813
+ metalLayer.frame = nativeView.bounds;
814
+ metalLayer.bounds = nativeView.bounds;
815
+ metalLayer.contentsScale = scale;
816
+ metalLayer.drawableSize = CGSizeMake(gWebGL.widthPx, gWebGL.heightPx);
817
+ metalLayer.needsDisplayOnBoundsChange = YES;
818
+ }
819
+ [CATransaction commit];
820
+ return static_cast<double>(targetBounds.size.width) / static_cast<double>(targetBounds.size.height);
821
+ }
822
+
823
+ // Re-assert the presentation properties ANGLE may reset when it adopts the
824
+ // CAMetalLayer as its window surface, so nextDrawable keeps blocking (never
825
+ // drops a frame to black) for the surface ANGLE actually renders into.
826
+ static void configureMetalLayerForPresentation(CAMetalLayer *layer) {
827
+ if (!layer) return;
828
+ layer.allowsNextDrawableTimeout = NO;
829
+ layer.maximumDrawableCount = 3;
830
+ }
831
+
832
+ #else
833
+ static double syncHostViewSize(double fallbackAspect) {
834
+ return gWebGL.heightPx > 0 ? static_cast<double>(gWebGL.widthPx) / gWebGL.heightPx : fallbackAspect;
835
+ }
836
+ extern "C" void gea_three_webgl_update_size_uwp(double width, double height, double pixelRatio) {
837
+ gWebGL.widthPx = std::max(1, static_cast<int>(width));
838
+ gWebGL.heightPx = std::max(1, static_cast<int>(height));
839
+ gWebGL.devicePixelRatio = pixelRatio > 0.0 ? pixelRatio : 1.0;
840
+ }
841
+ #endif
842
+
843
+ #if defined(_WIN32)
844
+ // The UWP frame driver owns its EGL context exclusively until it returns to
845
+ // CoreWindow event dispatch. Other hosts retain per-call binding semantics.
846
+ static thread_local bool gUwpFrameContextCurrent = false;
847
+ #endif
848
+ static bool makeCurrent(const char *label) {
849
+ #if defined(_WIN32)
850
+ if (gUwpFrameContextCurrent) return true;
851
+ #endif
852
+ if (!gWebGL.ready && gWebGL.context == EGL_NO_CONTEXT) return false;
853
+ // THIS FRAME LOOP IS ALREADY PACED. macOS drives it from the window's
854
+ // CADisplayLink at the screen's own cadence (`macos_main.mm`
855
+ // `updateDisplayLinkRate` pins `preferredFrameRateRange` to
856
+ // `screen.maximumFramesPerSecond`), and the CAMetalLayer presents with
857
+ // `displaySyncEnabled`. EGL's DEFAULT swap interval of 1 adds a THIRD
858
+ // waiter: `eglSwapBuffers` blocks for a vsync that the display link has
859
+ // already accounted for. The two pacers beat against each other -- a frame
860
+ // that finishes just after its own vsync waits for the next one, so the
861
+ // period lands at 2 refreshes now and then. A reference app measured
862
+ // `fps=55.5 wall=18.00ms busy=3.23ms idle=14.77ms(disp=14.77)`: only 3.2 ms
863
+ // of work against a 16.67 ms budget, missing 60 Hz purely on presentation
864
+ // pacing. Interval 0 hands pacing to the display link alone; tearing is not
865
+ // reintroduced because the layer still syncs its own present.
866
+ // `GEA_ANGLE_SWAP_INTERVAL` overrides it (set `1` to restore the old
867
+ // double-paced behaviour) so both arms can be measured from one binary.
868
+ if (gWebGL.eglSwapInterval) {
869
+ const char *requested = std::getenv("GEA_ANGLE_SWAP_INTERVAL");
870
+ const EGLint interval = requested ? static_cast<EGLint>(std::atoi(requested)) : 0;
871
+ gWebGL.eglSwapInterval(gWebGL.display, interval);
872
+ }
873
+ if (gWebGL.eglMakeCurrent(gWebGL.display, gWebGL.surface, gWebGL.surface, gWebGL.context) == EGL_FALSE_VALUE) {
874
+ logEglFailure(label);
875
+ return false;
876
+ }
877
+ return true;
878
+ }
879
+
880
+ #if defined(_WIN32)
881
+ extern "C" bool gea_three_webgl_begin_frame_uwp() {
882
+ // Always rebind after the dispatcher, where arbitrary platform callbacks
883
+ // may have used a different context. No such callbacks run within the frame.
884
+ gUwpFrameContextCurrent = false;
885
+ if (!gWebGL.ready || !makeCurrent("eglMakeCurrent(frame)")) return false;
886
+ gUwpFrameContextCurrent = true;
887
+ return true;
888
+ }
889
+ extern "C" void gea_three_webgl_end_frame_uwp() {
890
+ gUwpFrameContextCurrent = false;
891
+ }
892
+ #endif
893
+
894
+ #if !defined(_WIN32)
895
+ static bool rebuildWindowSurface(const char *label) {
896
+ if (!gWebGL.eglCreateWindowSurface || !gWebGL.eglDestroySurface || !gWebGL.eglMakeCurrent ||
897
+ !gWebGL.metalLayer || !gWebGL.config || gWebGL.context == EGL_NO_CONTEXT) {
898
+ return false;
899
+ }
900
+ syncHostViewSize(static_cast<double>(gWebGL.widthPx) / static_cast<double>(std::max(1, gWebGL.heightPx)));
901
+ EGLSurface oldSurface = gWebGL.surface;
902
+ gWebGL.eglMakeCurrent(gWebGL.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
903
+ if (oldSurface != EGL_NO_SURFACE) gWebGL.eglDestroySurface(gWebGL.display, oldSurface);
904
+ gWebGL.surface = gWebGL.eglCreateWindowSurface(
905
+ gWebGL.display,
906
+ gWebGL.config,
907
+ (__bridge EGLNativeWindowType)gWebGL.metalLayer,
908
+ nullptr
909
+ );
910
+ if (gWebGL.surface == EGL_NO_SURFACE) {
911
+ logEglFailure(label);
912
+ return false;
913
+ }
914
+ if (!makeCurrent("eglMakeCurrent(rebound-window-surface)")) return false;
915
+ configureMetalLayerForPresentation(gWebGL.metalLayer);
916
+ if (gWebGL.glViewport) gWebGL.glViewport(0, 0, gWebGL.widthPx, gWebGL.heightPx);
917
+ // Re-issue a full clear into the freshly rebound surface so its backbuffer is
918
+ // initialised to the app's frame instead of presenting an uninitialised
919
+ // (black) drawable before the app's next render lands.
920
+ if (gWebGL.glClearColor && gWebGL.glClear) {
921
+ gWebGL.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
922
+ gWebGL.glClear(0x00004000 /* GL_COLOR_BUFFER_BIT */ | 0x00000100 /* GL_DEPTH_BUFFER_BIT */);
923
+ }
924
+ return true;
925
+ }
926
+
927
+ #endif
928
+
929
+ static bool loadGlSymbols() {
930
+ #define LOAD_GL(name, type) \
931
+ gWebGL.name = loadGl<type>(#name); \
932
+ if (!gWebGL.name) { \
933
+ std::fprintf(stderr, "[three-angle-metal] missing GL symbol %s\n", #name); \
934
+ return false; \
935
+ }
936
+ LOAD_GL(glActiveTexture, PFNGLACTIVETEXTUREPROC)
937
+ LOAD_GL(glAttachShader, PFNGLATTACHSHADERPROC)
938
+ LOAD_GL(glBindBuffer, PFNGLBINDBUFFERPROC)
939
+ LOAD_GL(glBindFramebuffer, PFNGLBINDFRAMEBUFFERPROC)
940
+ LOAD_GL(glBindRenderbuffer, PFNGLBINDRENDERBUFFERPROC)
941
+ LOAD_GL(glBindTexture, PFNGLBINDTEXTUREPROC)
942
+ LOAD_GL(glBindVertexArray, PFNGLBINDVERTEXARRAYPROC)
943
+ LOAD_GL(glBlendEquation, PFNGLBLENDEQUATIONPROC)
944
+ LOAD_GL(glBlendEquationSeparate, PFNGLBLENDEQUATIONSEPARATEPROC)
945
+ LOAD_GL(glBlendFunc, PFNGLBLENDFUNCPROC)
946
+ LOAD_GL(glBlendFuncSeparate, PFNGLBLENDFUNCSEPARATEPROC)
947
+ LOAD_GL(glBufferData, PFNGLBUFFERDATAPROC)
948
+ LOAD_GL(glBufferSubData, PFNGLBUFFERSUBDATAPROC)
949
+ LOAD_GL(glCopyBufferSubData, PFNGLCOPYBUFFERSUBDATAPROC)
950
+ LOAD_GL(glCheckFramebufferStatus, PFNGLCHECKFRAMEBUFFERSTATUSPROC)
951
+ LOAD_GL(glClear, PFNGLCLEARPROC)
952
+ LOAD_GL(glClearColor, PFNGLCLEARCOLORPROC)
953
+ LOAD_GL(glClearDepthf, PFNGLCLEARDEPTHFPROC)
954
+ LOAD_GL(glClearStencil, PFNGLCLEARSTENCILPROC)
955
+ LOAD_GL(glColorMask, PFNGLCOLORMASKPROC)
956
+ LOAD_GL(glCompileShader, PFNGLCOMPILESHADERPROC)
957
+ LOAD_GL(glCreateProgram, PFNGLCREATEPROGRAMPROC)
958
+ LOAD_GL(glCreateShader, PFNGLCREATESHADERPROC)
959
+ LOAD_GL(glCullFace, PFNGLCULLFACEPROC)
960
+ LOAD_GL(glDeleteBuffers, PFNGLDELETEBUFFERSPROC)
961
+ LOAD_GL(glDeleteFramebuffers, PFNGLDELETEFRAMEBUFFERSPROC)
962
+ LOAD_GL(glDeleteProgram, PFNGLDELETEPROGRAMPROC)
963
+ LOAD_GL(glDeleteRenderbuffers, PFNGLDELETERENDERBUFFERSPROC)
964
+ LOAD_GL(glDeleteShader, PFNGLDELETESHADERPROC)
965
+ LOAD_GL(glDeleteTextures, PFNGLDELETETEXTURESPROC)
966
+ LOAD_GL(glDeleteVertexArrays, PFNGLDELETEVERTEXARRAYSPROC)
967
+ LOAD_GL(glDepthFunc, PFNGLDEPTHFUNCPROC)
968
+ LOAD_GL(glDepthMask, PFNGLDEPTHMASKPROC)
969
+ LOAD_GL(glDisable, PFNGLDISABLEPROC)
970
+ LOAD_GL(glDisableVertexAttribArray, PFNGLDISABLEVERTEXATTRIBARRAYPROC)
971
+ LOAD_GL(glDrawArrays, PFNGLDRAWARRAYSPROC)
972
+ LOAD_GL(glDrawArraysInstanced, PFNGLDRAWARRAYSINSTANCEDPROC)
973
+ LOAD_GL(glDrawBuffers, PFNGLDRAWBUFFERSPROC)
974
+ LOAD_GL(glDrawElements, PFNGLDRAWELEMENTSPROC)
975
+ LOAD_GL(glDrawElementsInstanced, PFNGLDRAWELEMENTSINSTANCEDPROC)
976
+ LOAD_GL(glEnable, PFNGLENABLEPROC)
977
+ LOAD_GL(glEnableVertexAttribArray, PFNGLENABLEVERTEXATTRIBARRAYPROC)
978
+ LOAD_GL(glFramebufferRenderbuffer, PFNGLFRAMEBUFFERRENDERBUFFERPROC)
979
+ LOAD_GL(glFramebufferTexture2D, PFNGLFRAMEBUFFERTEXTURE2DPROC)
980
+ LOAD_GL(glFrontFace, PFNGLFRONTFACEPROC)
981
+ LOAD_GL(glGenBuffers, PFNGLGENBUFFERSPROC)
982
+ LOAD_GL(glGenFramebuffers, PFNGLGENFRAMEBUFFERSPROC)
983
+ LOAD_GL(glGenRenderbuffers, PFNGLGENRENDERBUFFERSPROC)
984
+ LOAD_GL(glGenTextures, PFNGLGENTEXTURESPROC)
985
+ LOAD_GL(glGenVertexArrays, PFNGLGENVERTEXARRAYSPROC)
986
+ LOAD_GL(glGenerateMipmap, PFNGLGENERATEMIPMAPPROC)
987
+ LOAD_GL(glGetActiveAttrib, PFNGLGETACTIVEATTRIBPROC)
988
+ LOAD_GL(glGetActiveUniform, PFNGLGETACTIVEUNIFORMPROC)
989
+ LOAD_GL(glGetAttribLocation, PFNGLGETATTRIBLOCATIONPROC)
990
+ LOAD_GL(glGetError, PFNGLGETERRORPROC)
991
+ LOAD_GL(glGetIntegerv, PFNGLGETINTEGERVPROC)
992
+ LOAD_GL(glGetProgramiv, PFNGLGETPROGRAMIVPROC)
993
+ LOAD_GL(glGetShaderiv, PFNGLGETSHADERIVPROC)
994
+ LOAD_GL(glGetShaderInfoLog, PFNGLGETSHADERINFOLOGPROC)
995
+ LOAD_GL(glGetString, PFNGLGETSTRINGPROC)
996
+ LOAD_GL(glGetUniformLocation, PFNGLGETUNIFORMLOCATIONPROC)
997
+ LOAD_GL(glLineWidth, PFNGLLINEWIDTHPROC)
998
+ LOAD_GL(glLinkProgram, PFNGLLINKPROGRAMPROC)
999
+ LOAD_GL(glPixelStorei, PFNGLPIXELSTOREIPROC)
1000
+ LOAD_GL(glPolygonOffset, PFNGLPOLYGONOFFSETPROC)
1001
+ LOAD_GL(glReadPixels, PFNGLREADPIXELSPROC)
1002
+ LOAD_GL(glRenderbufferStorage, PFNGLRENDERBUFFERSTORAGEPROC)
1003
+ LOAD_GL(glScissor, PFNGLSCISSORPROC)
1004
+ LOAD_GL(glShaderSource, PFNGLSHADERSOURCEPROC)
1005
+ LOAD_GL(glStencilFunc, PFNGLSTENCILFUNCPROC)
1006
+ LOAD_GL(glStencilFuncSeparate, PFNGLSTENCILFUNCSEPARATEPROC)
1007
+ LOAD_GL(glStencilMask, PFNGLSTENCILMASKPROC)
1008
+ LOAD_GL(glStencilMaskSeparate, PFNGLSTENCILMASKSEPARATEPROC)
1009
+ LOAD_GL(glStencilOp, PFNGLSTENCILOPPROC)
1010
+ LOAD_GL(glStencilOpSeparate, PFNGLSTENCILOPSEPARATEPROC)
1011
+ LOAD_GL(glTexImage2D, PFNGLTEXIMAGE2DPROC)
1012
+ LOAD_GL(glTexParameterf, PFNGLTEXPARAMETERFPROC)
1013
+ LOAD_GL(glTexParameteri, PFNGLTEXPARAMETERIPROC)
1014
+ LOAD_GL(glTexSubImage2D, PFNGLTEXSUBIMAGE2DPROC)
1015
+ LOAD_GL(glTexStorage2D, PFNGLTEXSTORAGE2DPROC)
1016
+ LOAD_GL(glUniform1f, PFNGLUNIFORM1FPROC)
1017
+ LOAD_GL(glUniform1fv, PFNGLUNIFORM1FVPROC)
1018
+ LOAD_GL(glUniform1i, PFNGLUNIFORM1IPROC)
1019
+ LOAD_GL(glUniform1iv, PFNGLUNIFORM1IVPROC)
1020
+ LOAD_GL(glUniform1ui, PFNGLUNIFORM1UIPROC)
1021
+ LOAD_GL(glUniform1uiv, PFNGLUNIFORM1UIVPROC)
1022
+ LOAD_GL(glUniform2f, PFNGLUNIFORM2FPROC)
1023
+ LOAD_GL(glUniform2fv, PFNGLUNIFORM2FVPROC)
1024
+ LOAD_GL(glUniform2i, PFNGLUNIFORM2IPROC)
1025
+ LOAD_GL(glUniform2iv, PFNGLUNIFORM2IVPROC)
1026
+ LOAD_GL(glUniform2ui, PFNGLUNIFORM2UIPROC)
1027
+ LOAD_GL(glUniform2uiv, PFNGLUNIFORM2UIVPROC)
1028
+ LOAD_GL(glUniform3f, PFNGLUNIFORM3FPROC)
1029
+ LOAD_GL(glUniform3fv, PFNGLUNIFORM3FVPROC)
1030
+ LOAD_GL(glUniform3i, PFNGLUNIFORM3IPROC)
1031
+ LOAD_GL(glUniform3iv, PFNGLUNIFORM3IVPROC)
1032
+ LOAD_GL(glUniform3ui, PFNGLUNIFORM3UIPROC)
1033
+ LOAD_GL(glUniform3uiv, PFNGLUNIFORM3UIVPROC)
1034
+ LOAD_GL(glUniform4f, PFNGLUNIFORM4FPROC)
1035
+ LOAD_GL(glUniform4fv, PFNGLUNIFORM4FVPROC)
1036
+ LOAD_GL(glUniform4i, PFNGLUNIFORM4IPROC)
1037
+ LOAD_GL(glUniform4iv, PFNGLUNIFORM4IVPROC)
1038
+ LOAD_GL(glUniform4ui, PFNGLUNIFORM4UIPROC)
1039
+ LOAD_GL(glUniform4uiv, PFNGLUNIFORM4UIVPROC)
1040
+ LOAD_GL(glUniformMatrix2fv, PFNGLUNIFORMMATRIX2FVPROC)
1041
+ LOAD_GL(glUniformMatrix3fv, PFNGLUNIFORMMATRIX3FVPROC)
1042
+ LOAD_GL(glUniformMatrix4fv, PFNGLUNIFORMMATRIX4FVPROC)
1043
+ LOAD_GL(glUseProgram, PFNGLUSEPROGRAMPROC)
1044
+ LOAD_GL(glVertexAttribDivisor, PFNGLVERTEXATTRIBDIVISORPROC)
1045
+ LOAD_GL(glVertexAttribPointer, PFNGLVERTEXATTRIBPOINTERPROC)
1046
+ LOAD_GL(glViewport, PFNGLVIEWPORTPROC)
1047
+ #undef LOAD_GL
1048
+ return true;
1049
+ }
1050
+
1051
+ #if defined(_WIN32)
1052
+ extern "C" bool gea_three_webgl_attach_uwp(
1053
+ void *nativeWindow,
1054
+ #else
1055
+ extern "C" bool gea_three_webgl_attach(
1056
+ gea::apple::AppKit::NSView view,
1057
+ #endif
1058
+ double width,
1059
+ double height,
1060
+ double devicePixelRatio
1061
+ ) {
1062
+ geaTrapCheck("gea_three_webgl_attach");
1063
+ smokeLog("[three-angle-metal] WebGL host attach requested");
1064
+ if (gWebGL.ready) return true;
1065
+
1066
+ #if defined(_WIN32)
1067
+ gWebGL.egl = LoadPackagedLibrary(L"libEGL.dll", 0);
1068
+ gWebGL.gles = LoadPackagedLibrary(L"libGLESv2.dll", 0);
1069
+ #else
1070
+ static const char *const eglPaths[] = {
1071
+ "/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib",
1072
+ "/Applications/Cursor.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib",
1073
+ "/Applications/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Versions/149.0.7827.201/Libraries/libEGL.dylib",
1074
+ "/Applications/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Versions/149.0.7827.197/Libraries/libEGL.dylib",
1075
+ nullptr,
1076
+ };
1077
+ static const char *const glesPaths[] = {
1078
+ "/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libGLESv2.dylib",
1079
+ "/Applications/Cursor.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libGLESv2.dylib",
1080
+ "/Applications/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Versions/149.0.7827.201/Libraries/libGLESv2.dylib",
1081
+ "/Applications/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Versions/149.0.7827.197/Libraries/libGLESv2.dylib",
1082
+ nullptr,
1083
+ };
1084
+
1085
+ gWebGL.egl = openFirst("GEA_ANGLE_EGL_DYLIB", eglPaths);
1086
+ gWebGL.gles = openFirst("GEA_ANGLE_GLES_DYLIB", glesPaths);
1087
+ #endif
1088
+ if (!gWebGL.egl || !gWebGL.gles) {
1089
+ smokeLog("[three-angle-metal] ANGLE libEGL/libGLESv2 were not found");
1090
+ return false;
1091
+ }
1092
+
1093
+ gWebGL.eglGetProcAddress = loadSymbol<PFNEGLGETPROCADDRESS>(gWebGL.egl, "eglGetProcAddress");
1094
+ auto eglInitialize = loadSymbol<PFNEGLINITIALIZEPROC>(gWebGL.egl, "eglInitialize");
1095
+ auto eglBindAPI = loadSymbol<PFNEGLBINDAPIPROC>(gWebGL.egl, "eglBindAPI");
1096
+ auto eglChooseConfig = loadSymbol<PFNEGLCHOOSECONFIGPROC>(gWebGL.egl, "eglChooseConfig");
1097
+ gWebGL.eglCreateWindowSurface = loadSymbol<PFNEGLCREATEWINDOWSURFACEPROC>(gWebGL.egl, "eglCreateWindowSurface");
1098
+ gWebGL.eglCreatePbufferSurface = loadSymbol<PFNEGLCREATEPBUFFERSURFACEPROC>(gWebGL.egl, "eglCreatePbufferSurface");
1099
+ auto eglCreateContext = loadSymbol<PFNEGLCREATECONTEXTPROC>(gWebGL.egl, "eglCreateContext");
1100
+ gWebGL.eglDestroySurface = loadSymbol<PFNEGLDESTROYSURFACEPROC>(gWebGL.egl, "eglDestroySurface");
1101
+ gWebGL.eglGetError = loadSymbol<PFNEGLGETERRORPROC>(gWebGL.egl, "eglGetError");
1102
+ gWebGL.eglMakeCurrent = loadSymbol<PFNEGLMAKECURRENTPROC>(gWebGL.egl, "eglMakeCurrent");
1103
+ gWebGL.eglSwapBuffers = loadSymbol<PFNEGLSWAPBUFFERSPROC>(gWebGL.egl, "eglSwapBuffers");
1104
+ // Optional: an older EGL without it simply keeps the driver default below.
1105
+ gWebGL.eglSwapInterval = loadSymbol<PFNEGLSWAPINTERVALPROC>(gWebGL.egl, "eglSwapInterval");
1106
+ if (!gWebGL.eglGetProcAddress || !eglInitialize || !eglBindAPI || !eglChooseConfig ||
1107
+ !eglCreateContext || !gWebGL.eglMakeCurrent || !gWebGL.eglSwapBuffers) {
1108
+ smokeLog("[three-angle-metal] required EGL symbols were missing");
1109
+ return false;
1110
+ }
1111
+
1112
+ auto eglGetPlatformDisplayEXT =
1113
+ reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(gWebGL.eglGetProcAddress("eglGetPlatformDisplayEXT"));
1114
+ if (!eglGetPlatformDisplayEXT) {
1115
+ smokeLog("[three-angle-metal] eglGetPlatformDisplayEXT unavailable");
1116
+ return false;
1117
+ }
1118
+
1119
+ const EGLint displayAttrs[] = {
1120
+ EGL_PLATFORM_ANGLE_TYPE_ANGLE,
1121
+ #if defined(_WIN32)
1122
+ 0x3208, // EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE
1123
+ #else
1124
+ EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE,
1125
+ #endif
1126
+ EGL_NONE,
1127
+ };
1128
+ gWebGL.display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, displayAttrs);
1129
+ if (gWebGL.display == EGL_NO_DISPLAY) {
1130
+ logEglFailure("eglGetPlatformDisplayEXT");
1131
+ return false;
1132
+ }
1133
+
1134
+ EGLint major = 0;
1135
+ EGLint minor = 0;
1136
+ if (eglInitialize(gWebGL.display, &major, &minor) == EGL_FALSE_VALUE) {
1137
+ logEglFailure("eglInitialize");
1138
+ return false;
1139
+ }
1140
+ if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE_VALUE) {
1141
+ logEglFailure("eglBindAPI");
1142
+ return false;
1143
+ }
1144
+
1145
+ const EGLint configAttrs[] = {
1146
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
1147
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT | EGL_OPENGL_ES2_BIT,
1148
+ EGL_RED_SIZE, 8,
1149
+ EGL_GREEN_SIZE, 8,
1150
+ EGL_BLUE_SIZE, 8,
1151
+ EGL_ALPHA_SIZE, 8,
1152
+ EGL_DEPTH_SIZE, 24,
1153
+ EGL_STENCIL_SIZE, 8,
1154
+ EGL_NONE,
1155
+ };
1156
+ EGLConfig config = nullptr;
1157
+ EGLint configCount = 0;
1158
+ if (eglChooseConfig(gWebGL.display, configAttrs, &config, 1, &configCount) == EGL_FALSE_VALUE || configCount < 1) {
1159
+ logEglFailure("eglChooseConfig");
1160
+ return false;
1161
+ }
1162
+ gWebGL.config = config;
1163
+
1164
+ #if defined(_WIN32)
1165
+ gWebGL.nativeWindow = nativeWindow;
1166
+ gea_three_webgl_update_size_uwp(width, height, devicePixelRatio);
1167
+ if (gWebGL.eglCreateWindowSurface) {
1168
+ gWebGL.surface = gWebGL.eglCreateWindowSurface(gWebGL.display, config, nativeWindow, nullptr);
1169
+ }
1170
+ if (gWebGL.surface == EGL_NO_SURFACE) {
1171
+ logEglFailure("eglCreateWindowSurface(CoreWindow)");
1172
+ return false;
1173
+ }
1174
+ #else
1175
+ NSView *containerView = (__bridge NSView *)gea::apple::objc::object(view.handle);
1176
+ containerView.wantsLayer = YES;
1177
+ containerView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
1178
+ [containerView layoutSubtreeIfNeeded];
1179
+
1180
+ GeaAngleWebGLHostView *nativeView = [[GeaAngleWebGLHostView alloc] initWithFrame:containerView.bounds];
1181
+ [containerView addSubview:nativeView];
1182
+ gWebGL.hostView = nativeView;
1183
+ geaInstallPointerMonitor();
1184
+ gWebGL.devicePixelRatio = devicePixelRatio > 0.0 ? devicePixelRatio : 1.0;
1185
+ const double fallbackAspect = width > 0.0 && height > 0.0 ? width / height : 1.0;
1186
+
1187
+ CAMetalLayer *metalLayer = [nativeView.layer isKindOfClass:CAMetalLayer.class]
1188
+ ? (CAMetalLayer *)nativeView.layer
1189
+ : [CAMetalLayer layer];
1190
+ nativeView.layer = metalLayer;
1191
+ gWebGL.metalLayer = metalLayer;
1192
+ syncHostViewSize(fallbackAspect);
1193
+ [nativeView displayIfNeeded];
1194
+ [nativeView layoutSubtreeIfNeeded];
1195
+ [CATransaction flush];
1196
+
1197
+ if (gWebGL.eglCreateWindowSurface) {
1198
+ gWebGL.surface = gWebGL.eglCreateWindowSurface(gWebGL.display, config, (__bridge EGLNativeWindowType)metalLayer, nullptr);
1199
+ }
1200
+ if (gWebGL.surface == EGL_NO_SURFACE && gWebGL.eglCreatePbufferSurface) {
1201
+ logEglFailure("eglCreateWindowSurface");
1202
+ const EGLint surfaceAttrs[] = {
1203
+ EGL_WIDTH, static_cast<EGLint>(gWebGL.widthPx),
1204
+ EGL_HEIGHT, static_cast<EGLint>(gWebGL.heightPx),
1205
+ EGL_NONE,
1206
+ };
1207
+ gWebGL.surface = gWebGL.eglCreatePbufferSurface(gWebGL.display, config, surfaceAttrs);
1208
+ }
1209
+ if (gWebGL.surface == EGL_NO_SURFACE) {
1210
+ logEglFailure("eglCreatePbufferSurface");
1211
+ return false;
1212
+ }
1213
+ configureMetalLayerForPresentation(gWebGL.metalLayer);
1214
+ #endif
1215
+
1216
+ const EGLint contextAttrs[] = {
1217
+ EGL_CONTEXT_CLIENT_VERSION, 3,
1218
+ EGL_NONE,
1219
+ };
1220
+ gWebGL.context = eglCreateContext(gWebGL.display, config, EGL_NO_CONTEXT, contextAttrs);
1221
+ if (gWebGL.context == EGL_NO_CONTEXT) {
1222
+ logEglFailure("eglCreateContext");
1223
+ return false;
1224
+ }
1225
+ if (!makeCurrent("eglMakeCurrent")) return false;
1226
+ if (!loadGlSymbols()) return false;
1227
+
1228
+ gWebGL.ready = true;
1229
+ gWebGL.glViewport(0, 0, gWebGL.widthPx, gWebGL.heightPx);
1230
+ #if !defined(_WIN32)
1231
+ dispatch_async(dispatch_get_main_queue(), ^{
1232
+ if (gWebGL.ready && rebuildWindowSurface("eglCreateWindowSurface(deferred)")) {
1233
+ smokeLog("[three-angle-metal] WebGL EGL surface rebound at %dx%d", gWebGL.widthPx, gWebGL.heightPx);
1234
+ }
1235
+ });
1236
+ #endif
1237
+ smokeLog("[native-webgl-angle] context attached, EGL %d.%d at %dx%d", major, minor, gWebGL.widthPx, gWebGL.heightPx);
1238
+ return true;
1239
+ }
1240
+
1241
+ extern "C" double gea_three_webgl_sync_size(double fallbackAspect) {
1242
+ if (nativeFrameProfileEnabled()) {
1243
+ gProfileSizeSync = std::chrono::steady_clock::now();
1244
+ gProfileSizeSyncPending = true;
1245
+ }
1246
+ geaTrapCheck("gea_three_webgl_sync_size");
1247
+ if (std::getenv("GEA_WEBGL_HOST_DEBUG")) {
1248
+ smokeLog("[three-angle-metal] gea_three_webgl_sync_size begin fallback=%g ready=%d size=%dx%d",
1249
+ fallbackAspect, gWebGL.ready ? 1 : 0, gWebGL.widthPx, gWebGL.heightPx);
1250
+ }
1251
+ const double aspect = syncHostViewSize(fallbackAspect);
1252
+ gGpuFrameProfile.start();
1253
+ if (gWebGL.ready && gWebGL.glViewport) gWebGL.glViewport(0, 0, gWebGL.widthPx, gWebGL.heightPx);
1254
+ if (std::getenv("GEA_WEBGL_HOST_DEBUG")) {
1255
+ smokeLog("[three-angle-metal] gea_three_webgl_sync_size end aspect=%g size=%dx%d",
1256
+ aspect, gWebGL.widthPx, gWebGL.heightPx);
1257
+ }
1258
+ return aspect;
1259
+ }
1260
+
1261
+ extern "C" double gea_three_webgl_width() {
1262
+ geaTrapCheck("gea_three_webgl_width");
1263
+ return static_cast<double>(gWebGL.widthPx);
1264
+ }
1265
+
1266
+ extern "C" double gea_three_webgl_height() {
1267
+ geaTrapCheck("gea_three_webgl_height");
1268
+ return static_cast<double>(gWebGL.heightPx);
1269
+ }
1270
+
1271
+ // The backing scale the host actually applied (window backingScaleFactor,
1272
+ // refreshed by every syncHostViewSize) — the native analog of
1273
+ // window.devicePixelRatio. widthPx/heightPx are DEVICE pixels; dividing by
1274
+ // this yields the CSS-pixel (logical) size the web build lays out in.
1275
+ extern "C" double gea_three_webgl_device_pixel_ratio() {
1276
+ geaTrapCheck("gea_three_webgl_device_pixel_ratio");
1277
+ return gWebGL.devicePixelRatio > 0.0 ? gWebGL.devicePixelRatio : 1.0;
1278
+ }
1279
+
1280
+ extern "C" void gea_three_webgl_swap() {
1281
+ geaTrapCheck("gea_three_webgl_swap");
1282
+ if (!gWebGL.ready || !makeCurrent("eglMakeCurrent(swap)")) return;
1283
+ gGpuFrameProfile.finish();
1284
+ // GEA_WEBGL_FPS=1 — rolling real-time present cadence to stderr. Measures the
1285
+ // wall-clock interval between successive swaps (the true windowed frame rate,
1286
+ // which the vsync-free headless [gea.perf] cannot see). Harmless when unset.
1287
+ if (std::getenv("GEA_WEBGL_FPS")) {
1288
+ using clock = std::chrono::steady_clock;
1289
+ static clock::time_point last = clock::now();
1290
+ static clock::time_point windowStart = last;
1291
+ static int frames = 0;
1292
+ const clock::time_point nowTp = clock::now();
1293
+ ++frames;
1294
+ const double windowMs =
1295
+ std::chrono::duration<double, std::milli>(nowTp - windowStart).count();
1296
+ if (windowMs >= 1000.0) {
1297
+ const double dtMs =
1298
+ std::chrono::duration<double, std::milli>(nowTp - last).count();
1299
+ smokeLog("[gea.fps] %.1f fps over %d frames (%.0fms), last dt=%.2fms",
1300
+ frames * 1000.0 / windowMs, frames, windowMs, dtMs);
1301
+ frames = 0;
1302
+ windowStart = nowTp;
1303
+ }
1304
+ last = nowTp;
1305
+ }
1306
+ if (std::getenv("GEA_WEBGL_SAMPLE_PIXELS") && gWebGL.glReadPixels && gWebGL.glGetError && gWebGL.glGetIntegerv) {
1307
+ static int sampleCount = 0;
1308
+ if (sampleCount < 16 || sampleCount % 60 == 0) {
1309
+ unsigned char pixel[4] = {0, 0, 0, 0};
1310
+ GLint viewport[4] = {0, 0, 0, 0};
1311
+ GLint framebuffer = 0;
1312
+ gWebGL.glGetIntegerv(GL_VIEWPORT_VALUE, viewport);
1313
+ gWebGL.glGetIntegerv(GL_FRAMEBUFFER_BINDING_VALUE, &framebuffer);
1314
+ gWebGL.glReadPixels(
1315
+ std::max(0, gWebGL.widthPx / 2),
1316
+ std::max(0, gWebGL.heightPx / 2),
1317
+ 1,
1318
+ 1,
1319
+ GL_RGBA_VALUE,
1320
+ GL_UNSIGNED_BYTE,
1321
+ pixel
1322
+ );
1323
+ const GLenum readError = gWebGL.glGetError();
1324
+ smokeLog(
1325
+ "[three-angle-metal] swap sample frame=%d surface=%dx%d viewport=%d,%d,%d,%d framebuffer=%d center=%u,%u,%u,%u glError=0x%04x",
1326
+ sampleCount,
1327
+ gWebGL.widthPx,
1328
+ gWebGL.heightPx,
1329
+ viewport[0],
1330
+ viewport[1],
1331
+ viewport[2],
1332
+ viewport[3],
1333
+ framebuffer,
1334
+ pixel[0],
1335
+ pixel[1],
1336
+ pixel[2],
1337
+ pixel[3],
1338
+ readError
1339
+ );
1340
+ }
1341
+ sampleCount++;
1342
+ }
1343
+ // TEMP-DIAG: GEA_THREE_VIEW_DUMP=1 — at frame 60, log the window's full
1344
+ // subview tree and a hit-test grid so overlay views that would swallow
1345
+ // mouse events are visible without Accessibility permission.
1346
+ #if !defined(_WIN32)
1347
+ if (std::getenv("GEA_THREE_VIEW_DUMP")) {
1348
+ static int viewDumpCount = 0;
1349
+ if (++viewDumpCount == 60) {
1350
+ NSView *host = gWebGL.hostView;
1351
+ NSWindow *window = host ? host.window : nil;
1352
+ NSView *content = window ? window.contentView : nil;
1353
+ if (content) {
1354
+ __block void (^walk)(NSView *, int) = nil;
1355
+ walk = ^(NSView *view, int depth) {
1356
+ smokeLog("[view-dump] %*s%s frame=(%.0f,%.0f %.0fx%.0f) hidden=%d", depth * 2, "",
1357
+ object_getClassName(view), view.frame.origin.x, view.frame.origin.y,
1358
+ view.frame.size.width, view.frame.size.height, view.isHidden ? 1 : 0);
1359
+ for (NSView *sub in view.subviews) walk(sub, depth + 1);
1360
+ };
1361
+ walk(content, 0);
1362
+ const CGFloat w = content.bounds.size.width;
1363
+ const CGFloat h = content.bounds.size.height;
1364
+ for (int gy = 1; gy < 8; gy += 2) {
1365
+ for (int gx = 1; gx < 8; gx += 2) {
1366
+ const NSPoint inContent = NSMakePoint(w * gx / 8.0, h * gy / 8.0);
1367
+ const NSPoint inSuper = [content.superview convertPoint:inContent fromView:content];
1368
+ NSView *hit = [content hitTest:inSuper];
1369
+ smokeLog("[hit-grid] (%.0f,%.0f) -> %s", inContent.x, inContent.y,
1370
+ hit ? object_getClassName(hit) : "nil");
1371
+ }
1372
+ }
1373
+ } else {
1374
+ smokeLog("[view-dump] no window/content view");
1375
+ }
1376
+ }
1377
+ }
1378
+ #endif
1379
+ // Debug frame dump: GEA_WEBGL_DUMP_FRAME=<path-prefix> writes the full
1380
+ // framebuffer of frames 90/180/300 as PPM (P6) — visual ground truth for
1381
+ // native-vs-browser comparisons without needing a window screenshot.
1382
+ if (const char *dumpPrefix = std::getenv("GEA_WEBGL_DUMP_FRAME"); dumpPrefix && gWebGL.glReadPixels) {
1383
+ static int dumpCount = 0;
1384
+ ++dumpCount;
1385
+ if (dumpCount == 90 || dumpCount == 180 || dumpCount == 300) {
1386
+ const int w = gWebGL.widthPx;
1387
+ const int h = gWebGL.heightPx;
1388
+ std::vector<unsigned char> rgba(static_cast<size_t>(w) * h * 4);
1389
+ gWebGL.glReadPixels(0, 0, w, h, GL_RGBA_VALUE, GL_UNSIGNED_BYTE, rgba.data());
1390
+ char path[512];
1391
+ std::snprintf(path, sizeof path, "%s-f%d.ppm", dumpPrefix, dumpCount);
1392
+ if (FILE *file = std::fopen(path, "wb")) {
1393
+ std::fprintf(file, "P6\n%d %d\n255\n", w, h);
1394
+ for (int y = h - 1; y >= 0; --y) {
1395
+ const unsigned char *row = rgba.data() + static_cast<size_t>(y) * w * 4;
1396
+ for (int x = 0; x < w; ++x) std::fwrite(row + x * 4, 1, 3, file);
1397
+ }
1398
+ std::fclose(file);
1399
+ smokeLog("[three-angle-metal] dumped frame %d to %s", dumpCount, path);
1400
+ }
1401
+ }
1402
+ }
1403
+ // Measure presentation separately from the display-link callback. The
1404
+ // latter includes this wait and cannot by itself distinguish frame work
1405
+ // from drawable/GPU backpressure. No clock reads run when profiling is off.
1406
+ const bool profile = nativeFrameProfileEnabled();
1407
+ using Clock = std::chrono::steady_clock;
1408
+ const auto swapBegin = profile ? Clock::now() : Clock::time_point{};
1409
+ if (gWebGL.eglSwapBuffers(gWebGL.display, gWebGL.surface) == EGL_FALSE_VALUE) {
1410
+ logEglFailure("eglSwapBuffers");
1411
+ }
1412
+ if (profile) {
1413
+ const auto end = Clock::now();
1414
+ static auto windowStart = swapBegin;
1415
+ static double totalMs = 0.0;
1416
+ static double frameWorkMs = 0.0;
1417
+ static unsigned timedFrames = 0;
1418
+ static double maximumMs = 0.0;
1419
+ static unsigned frames = 0;
1420
+ const double elapsedMs = std::chrono::duration<double, std::milli>(end - swapBegin).count();
1421
+ totalMs += elapsedMs;
1422
+ if (gProfileSizeSyncPending) {
1423
+ frameWorkMs += std::chrono::duration<double, std::milli>(swapBegin - gProfileSizeSync).count();
1424
+ ++timedFrames;
1425
+ gProfileSizeSyncPending = false;
1426
+ }
1427
+ maximumMs = std::max(maximumMs, elapsedMs);
1428
+ ++frames;
1429
+ if (std::chrono::duration<double>(end - windowStart).count() >= 1.0) {
1430
+ smokeLog("[gea.swap-profile] swap_mean_ms=%.3f swap_max_ms=%.3f size_sync_to_swap_mean_ms=%.3f timed_frames=%u frames=%u surface=%dx%d",
1431
+ totalMs / frames, maximumMs, timedFrames ? frameWorkMs / timedFrames : 0.0, timedFrames, frames, gWebGL.widthPx, gWebGL.heightPx);
1432
+ frames = 0;
1433
+ totalMs = frameWorkMs = maximumMs = 0.0;
1434
+ timedFrames = 0;
1435
+ windowStart = end;
1436
+ }
1437
+ }
1438
+ }
1439
+
1440
+ enum WebGLOp {
1441
+ OP_CLEAR_COLOR = 1,
1442
+ OP_CLEAR = 2,
1443
+ OP_VIEWPORT = 3,
1444
+ OP_ENABLE = 4,
1445
+ OP_DISABLE = 5,
1446
+ OP_DEPTH_FUNC = 6,
1447
+ OP_DEPTH_MASK = 7,
1448
+ OP_COLOR_MASK = 8,
1449
+ OP_BLEND_FUNC = 9,
1450
+ OP_BLEND_FUNC_SEPARATE = 10,
1451
+ OP_BLEND_EQUATION = 11,
1452
+ OP_BLEND_EQUATION_SEPARATE = 12,
1453
+ OP_CULL_FACE = 13,
1454
+ OP_FRONT_FACE = 14,
1455
+ OP_CREATE_BUFFER = 15,
1456
+ OP_DELETE_BUFFER = 16,
1457
+ OP_BIND_BUFFER = 17,
1458
+ OP_CREATE_SHADER = 20,
1459
+ OP_SHADER_SOURCE = 21,
1460
+ OP_COMPILE_SHADER = 22,
1461
+ OP_GET_SHADER_PARAMETER = 23,
1462
+ OP_DELETE_SHADER = 24,
1463
+ OP_CREATE_PROGRAM = 25,
1464
+ OP_ATTACH_SHADER = 26,
1465
+ OP_LINK_PROGRAM = 27,
1466
+ OP_GET_PROGRAM_PARAMETER = 28,
1467
+ OP_DELETE_PROGRAM = 29,
1468
+ OP_USE_PROGRAM = 30,
1469
+ OP_GET_ATTRIB_LOCATION = 31,
1470
+ OP_GET_UNIFORM_LOCATION = 32,
1471
+ OP_ENABLE_VERTEX_ATTRIB_ARRAY = 33,
1472
+ OP_DISABLE_VERTEX_ATTRIB_ARRAY = 34,
1473
+ OP_VERTEX_ATTRIB_POINTER = 35,
1474
+ OP_UNIFORM_1F = 37,
1475
+ OP_UNIFORM_1I = 38,
1476
+ OP_UNIFORM_2F = 39,
1477
+ OP_UNIFORM_3F = 40,
1478
+ OP_UNIFORM_4F = 41,
1479
+ OP_DRAW_ELEMENTS = 42,
1480
+ OP_DRAW_ARRAYS = 43,
1481
+ OP_ACTIVE_TEXTURE = 44,
1482
+ OP_CREATE_TEXTURE = 45,
1483
+ OP_DELETE_TEXTURE = 46,
1484
+ OP_BIND_TEXTURE = 47,
1485
+ OP_TEX_PARAMETERI = 48,
1486
+ OP_PIXEL_STOREI = 49,
1487
+ OP_GENERATE_MIPMAP = 52,
1488
+ OP_CREATE_VERTEX_ARRAY = 53,
1489
+ OP_BIND_VERTEX_ARRAY = 54,
1490
+ OP_DELETE_VERTEX_ARRAY = 55,
1491
+ OP_GET_ERROR = 56,
1492
+ OP_SCISSOR = 57,
1493
+ OP_LINE_WIDTH = 58,
1494
+ OP_POLYGON_OFFSET = 59,
1495
+ OP_CLEAR_DEPTH = 60,
1496
+ OP_CLEAR_STENCIL = 61,
1497
+ OP_STENCIL_MASK = 62,
1498
+ OP_STENCIL_FUNC = 63,
1499
+ OP_STENCIL_OP = 64,
1500
+ OP_STENCIL_FUNC_SEPARATE = 65,
1501
+ OP_STENCIL_OP_SEPARATE = 66,
1502
+ OP_STENCIL_MASK_SEPARATE = 67,
1503
+ OP_CREATE_FRAMEBUFFER = 68,
1504
+ OP_BIND_FRAMEBUFFER = 69,
1505
+ OP_DELETE_FRAMEBUFFER = 70,
1506
+ OP_FRAMEBUFFER_TEXTURE_2D = 71,
1507
+ OP_CHECK_FRAMEBUFFER_STATUS = 72,
1508
+ OP_CREATE_RENDERBUFFER = 73,
1509
+ OP_BIND_RENDERBUFFER = 74,
1510
+ OP_RENDERBUFFER_STORAGE = 75,
1511
+ OP_FRAMEBUFFER_RENDERBUFFER = 76,
1512
+ OP_DELETE_RENDERBUFFER = 77,
1513
+ OP_DRAW_BUFFERS = 78,
1514
+ };
1515
+
1516
+ static GLuint generatedBuffer() {
1517
+ GLuint handle = 0;
1518
+ gWebGL.glGenBuffers(1, &handle);
1519
+ return handle;
1520
+ }
1521
+
1522
+ static GLuint generatedTexture() {
1523
+ GLuint handle = 0;
1524
+ gWebGL.glGenTextures(1, &handle);
1525
+ return handle;
1526
+ }
1527
+
1528
+ static GLuint generatedVertexArray() {
1529
+ GLuint handle = 0;
1530
+ gWebGL.glGenVertexArrays(1, &handle);
1531
+ return handle;
1532
+ }
1533
+
1534
+ static GLuint generatedFramebuffer() {
1535
+ GLuint handle = 0;
1536
+ gWebGL.glGenFramebuffers(1, &handle);
1537
+ return handle;
1538
+ }
1539
+
1540
+ static GLuint generatedRenderbuffer() {
1541
+ GLuint handle = 0;
1542
+ gWebGL.glGenRenderbuffers(1, &handle);
1543
+ return handle;
1544
+ }
1545
+
1546
+ static bool ensureWebGLCurrent(const char *step) {
1547
+ return gWebGL.ready && makeCurrent(step);
1548
+ }
1549
+
1550
+ static GLenum asGLenum(double value) { return static_cast<GLenum>(value); }
1551
+ static GLint asGLint(double value) { return static_cast<GLint>(value); }
1552
+ static GLsizei asGLsizei(double value) { return static_cast<GLsizei>(value); }
1553
+ static GLuint asGLuint(double value) { return static_cast<GLuint>(value); }
1554
+ static GLbitfield asGLbitfield(double value) { return static_cast<GLbitfield>(value); }
1555
+
1556
+ static int vectorTypeCodeForGLType(double value) {
1557
+ const GLenum type = asGLenum(value);
1558
+ if (type == 0x1403) return 2; // UNSIGNED_SHORT
1559
+ if (type == 0x1405) return 3; // UNSIGNED_INT
1560
+ if (type == 0x1401) return 4; // UNSIGNED_BYTE
1561
+ return 1;
1562
+ }
1563
+
1564
+ #if defined(GEA_PROFILE_ALLOCATIONS) || defined(GEA_PROFILE_NATIVE_FRAMES)
1565
+ namespace {
1566
+ using ProfileClock = std::chrono::steady_clock;
1567
+ static double nativeThreadCpuMs() {
1568
+ #if defined(_WIN32)
1569
+ FILETIME created{}, exited{}, kernel{}, user{};
1570
+ if (!GetThreadTimes(GetCurrentThread(), &created, &exited, &kernel, &user)) return 0;
1571
+ const auto ticks = [](const FILETIME& t) { return (static_cast<std::uint64_t>(t.dwHighDateTime) << 32) | t.dwLowDateTime; };
1572
+ return static_cast<double>(ticks(kernel) + ticks(user)) / 10000.0;
1573
+ #else
1574
+ return 0;
1575
+ #endif
1576
+ }
1577
+ struct NativeFrameProfile {
1578
+ double phases[4]{}, phaseCpu[4]{}, cpuStarted = 0, host[4]{};
1579
+ std::uint64_t calls[4]{};
1580
+ unsigned phase = 0, frames = 0;
1581
+ ProfileClock::time_point started{};
1582
+ };
1583
+ thread_local NativeFrameProfile nativeProfile;
1584
+ struct NativeCallTime { const char* name = nullptr; double ms = 0; std::uint64_t calls = 0; };
1585
+ thread_local NativeCallTime nativeCalls[128];
1586
+ struct NativeHostProfile {
1587
+ unsigned category;
1588
+ const char* name;
1589
+ ProfileClock::time_point start = ProfileClock::now();
1590
+ explicit NativeHostProfile(const char* step) : category(std::strstr(step, "draw") ? 0 : std::strstr(step, "uniform") ? 1 : std::strstr(step, "buffer") || std::strstr(step, "Buffer") || std::strstr(step, "tex") ? 2 : 3), name(step) {}
1591
+ ~NativeHostProfile() {
1592
+ const double elapsed = std::chrono::duration<double, std::milli>(ProfileClock::now() - start).count();
1593
+ nativeProfile.host[category] += elapsed;
1594
+ for (auto& row : nativeCalls) {
1595
+ if (!row.name) row.name = name;
1596
+ if (row.name == name || std::strcmp(row.name, name) == 0) { row.ms += elapsed; ++row.calls; break; }
1597
+ }
1598
+ ++nativeProfile.calls[category];
1599
+ }
1600
+ };
1601
+ }
1602
+ #define GEA_PROFILE_HOST(step) NativeHostProfile geaHostScope(step)
1603
+ #else
1604
+ #define GEA_PROFILE_HOST(step)
1605
+ #endif
1606
+
1607
+ extern "C" void gea_three_native_profile_phase(double phaseValue) {
1608
+ #if defined(GEA_PROFILE_ALLOCATIONS) || defined(GEA_PROFILE_NATIVE_FRAMES)
1609
+ auto& p = nativeProfile;
1610
+ const auto now = ProfileClock::now();
1611
+ const double cpu = nativeThreadCpuMs();
1612
+ const unsigned next = static_cast<unsigned>(phaseValue);
1613
+ if (p.phase) p.phaseCpu[p.phase] += cpu - p.cpuStarted;
1614
+ if (p.phase) p.phases[p.phase] += std::chrono::duration<double, std::milli>(now - p.started).count();
1615
+ if (p.phase == 2 && next == 0 && ++p.frames == 60) {
1616
+ std::fprintf(stderr, "[gea.regions] frames=%u update_ms=%.6f render_ms=%.6f scene_ms=%.6f update_cpu_ms=%.6f render_cpu_ms=%.6f scene_cpu_ms=%.6f draw_ms=%.6f uniform_ms=%.6f upload_ms=%.6f state_ms=%.6f draw_calls=%llu uniform_calls=%llu upload_calls=%llu state_calls=%llu\n",
1617
+ p.frames, p.phases[1] / p.frames, p.phases[2] / p.frames, p.phases[3] / p.frames, p.phaseCpu[1] / p.frames, p.phaseCpu[2] / p.frames, p.phaseCpu[3] / p.frames,
1618
+ p.host[0] / p.frames, p.host[1] / p.frames, p.host[2] / p.frames, p.host[3] / p.frames,
1619
+ p.calls[0], p.calls[1], p.calls[2], p.calls[3]);
1620
+ for (auto& row : nativeCalls) {
1621
+ if (row.calls) std::fprintf(stderr, "[gea.host] name=%s calls=%llu ms=%.6f\n", row.name, row.calls, row.ms);
1622
+ row = {};
1623
+ }
1624
+ p = {};
1625
+ }
1626
+ p.phase = next; p.started = now; p.cpuStarted = cpu;
1627
+ #endif
1628
+ }
1629
+
1630
+ #define GEA_WEBGL_VOID(step) GEA_PROFILE_HOST(step); if (!ensureWebGLCurrent(step)) return
1631
+ #define GEA_WEBGL_DOUBLE(step) GEA_PROFILE_HOST(step); if (!ensureWebGLCurrent(step)) return 0.0
1632
+
1633
+ // GEA_WEBGL_TRAP=1: poll glGetError at every intrinsic ENTRY and log the
1634
+ // first offenders. GL errors are sticky until polled, so the intrinsic that
1635
+ // OBSERVES the error ran right after the intrinsic that PRODUCED it — the
1636
+ // log sequence pins the producer of the chronic per-frame GL_INVALID_OPERATION.
1637
+ static void geaTrapCheck(const char *where) {
1638
+ if (!nativeWebGLTrapEnabled() || !gWebGL.glGetError || !gWebGL.ready) return;
1639
+ static int logged = 0;
1640
+ if (logged >= 60) return;
1641
+ const GLenum err = gWebGL.glGetError();
1642
+ if (err == 0) return;
1643
+ ++logged;
1644
+ smokeLog("[gea-webgl-trap] error=0x%04x observed entering %s", err, where);
1645
+ }
1646
+
1647
+ #if defined(GEA_PROFILE_ALLOCATIONS) || defined(GEA_PROFILE_NATIVE_FRAMES) || defined(_WIN32)
1648
+ // Streaming policies preserve the GL object identity (and VAO
1649
+ // references). Only known full vertex/index replacements may discard storage.
1650
+ // Partial updates and other targets always retain the ordinary subdata path.
1651
+ struct NativeBufferState { GLsizeiptr size = 0; GLenum usage = 0; GLuint staging[3]{}; unsigned next = 0; };
1652
+ static GLuint gArrayBuffer = 0, gCopyReadBuffer = 0, gCopyWriteBuffer = 0;
1653
+ static std::unordered_map<GLuint, NativeBufferState> gBuffers;
1654
+ static GLuint nativeUploadBuffer(GLenum target) {
1655
+ if (target == 0x8892) return gArrayBuffer;
1656
+ if (target == 0x8893) {
1657
+ // The element binding belongs to the current VAO. Query it instead of
1658
+ // shadowing one global binding that becomes stale on a VAO switch.
1659
+ GLint buffer = 0;
1660
+ gWebGL.glGetIntegerv(0x8895, &buffer);
1661
+ return static_cast<GLuint>(buffer);
1662
+ }
1663
+ return 0;
1664
+ }
1665
+ static int nativeBufferStreamingMode() {
1666
+ static const int mode = [] {
1667
+ const char* value = std::getenv("GEA_WEBGL_BUFFER_STREAMING");
1668
+ if (value) return std::atoi(value);
1669
+ #if defined(_WIN32)
1670
+ return 8; // D3D11: stage vertex writes; retain CPU-accessible index data.
1671
+ #else
1672
+ return 0;
1673
+ #endif
1674
+ }();
1675
+ return mode;
1676
+ }
1677
+ static GLint nativeUploadUniformLimit() {
1678
+ static const GLint limit = [] {
1679
+ GLint value = 0;
1680
+ gWebGL.glGetIntegerv(0x8A30, &value); // MAX_UNIFORM_BLOCK_SIZE
1681
+ return value;
1682
+ }();
1683
+ return limit;
1684
+ }
1685
+ static void bindNativeBuffer(GLenum target, GLuint handle) {
1686
+ gWebGL.glBindBuffer(target, handle);
1687
+ #if !defined(GEA_PROFILE_ALLOCATIONS) && !defined(GEA_PROFILE_NATIVE_FRAMES)
1688
+ if (nativeBufferStreamingMode() == 0) return;
1689
+ #endif
1690
+ if (target == 0x8892) gArrayBuffer = handle;
1691
+ if (target == 0x8F36) gCopyReadBuffer = handle;
1692
+ if (target == 0x8F37) gCopyWriteBuffer = handle;
1693
+ }
1694
+ static void deleteNativeBuffer(GLuint handle) {
1695
+ gWebGL.glDeleteBuffers(1, &handle);
1696
+ #if !defined(GEA_PROFILE_ALLOCATIONS) && !defined(GEA_PROFILE_NATIVE_FRAMES)
1697
+ if (nativeBufferStreamingMode() == 0) return;
1698
+ #endif
1699
+ const auto found = gBuffers.find(handle);
1700
+ if (found != gBuffers.end()) gWebGL.glDeleteBuffers(3, found->second.staging);
1701
+ gBuffers.erase(handle);
1702
+ if (gArrayBuffer == handle) gArrayBuffer = 0;
1703
+ if (gCopyReadBuffer == handle) gCopyReadBuffer = 0;
1704
+ if (gCopyWriteBuffer == handle) gCopyWriteBuffer = 0;
1705
+ }
1706
+ static void nativeBufferData(GLenum target, GLsizeiptr size, const void* bytes, GLenum usage) {
1707
+ gWebGL.glBufferData(target, size, bytes, usage);
1708
+ #if !defined(GEA_PROFILE_ALLOCATIONS) && !defined(GEA_PROFILE_NATIVE_FRAMES)
1709
+ if (nativeBufferStreamingMode() == 0) return;
1710
+ #endif
1711
+ const GLuint buffer = nativeUploadBuffer(target);
1712
+ if (buffer && size >= 0) {
1713
+ auto& state = gBuffers[buffer];
1714
+ if (state.size != size) { gWebGL.glDeleteBuffers(3, state.staging); state = {}; }
1715
+ state.size = size; state.usage = usage;
1716
+ }
1717
+ }
1718
+ static void nativeBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* bytes) {
1719
+ #if !defined(GEA_PROFILE_ALLOCATIONS) && !defined(GEA_PROFILE_NATIVE_FRAMES)
1720
+ if (nativeBufferStreamingMode() == 0) { gWebGL.glBufferSubData(target, offset, size, bytes); return; }
1721
+ #endif
1722
+ const GLuint buffer = nativeUploadBuffer(target);
1723
+ const auto found = gBuffers.find(buffer);
1724
+ const bool full = found != gBuffers.end() && offset == 0 && size > 0 && size == found->second.size;
1725
+ const int mode = nativeBufferStreamingMode();
1726
+ // 1..4 retain the original vertex-only experiments. 5/6 include indices;
1727
+ // 7 isolates index streaming; 8 combines vertex staging with dynamic indices.
1728
+ // 9 writes small vertex staging buffers through uniform storage. ANGLE D3D11
1729
+ // maps this storage with WRITE_DISCARD instead of waiting on a staging map.
1730
+ const bool eligible = full && (mode >= 5 || target == 0x8892);
1731
+ #if defined(GEA_PROFILE_ALLOCATIONS) || defined(GEA_PROFILE_NATIVE_FRAMES)
1732
+ const auto start = ProfileClock::now();
1733
+ #endif
1734
+ if (eligible && (mode == 4 || mode == 6 || ((mode == 8 || mode == 9) && target == 0x8892)) && gWebGL.glCopyBufferSubData) {
1735
+ auto& state = found->second;
1736
+ if (!state.staging[0]) {
1737
+ gWebGL.glGenBuffers(3, state.staging);
1738
+ for (GLuint staging : state.staging) {
1739
+ gWebGL.glBindBuffer(0x8F36, staging);
1740
+ gWebGL.glBufferData(0x8F36, size, nullptr, 0x88E4);
1741
+ }
1742
+ }
1743
+ // Rotate upload storage while keeping the destination's object identity,
1744
+ // so every existing VAO continues referencing the correct buffer.
1745
+ const GLuint staging = state.staging[state.next];
1746
+ gWebGL.glBindBuffer(0x8F36, staging);
1747
+ state.next = (state.next + 1) % 3;
1748
+ if (mode == 9 && size <= nativeUploadUniformLimit()) {
1749
+ // Query rather than shadow this binding: bindBufferBase/Range may also
1750
+ // change it. The destination GL object, usage and VAOs remain untouched.
1751
+ GLint uniform = 0;
1752
+ gWebGL.glGetIntegerv(0x8A28, &uniform); // UNIFORM_BUFFER_BINDING
1753
+ gWebGL.glBindBuffer(0x8A11, staging); // UNIFORM_BUFFER
1754
+ gWebGL.glBufferSubData(0x8A11, 0, size, bytes);
1755
+ gWebGL.glBindBuffer(0x8A11, static_cast<GLuint>(uniform));
1756
+ } else {
1757
+ gWebGL.glBufferSubData(0x8F36, 0, size, bytes);
1758
+ }
1759
+ gWebGL.glBindBuffer(0x8F37, buffer);
1760
+ gWebGL.glCopyBufferSubData(0x8F36, 0x8F37, 0, 0, size);
1761
+ gWebGL.glBindBuffer(0x8F36, gCopyReadBuffer);
1762
+ gWebGL.glBindBuffer(0x8F37, gCopyWriteBuffer);
1763
+ } else if (eligible && mode == 1) {
1764
+ gWebGL.glBufferData(target, size, nullptr, found->second.usage);
1765
+ gWebGL.glBufferSubData(target, offset, size, bytes);
1766
+ } else if (eligible && (mode == 3 || mode == 5 || ((mode == 7 || mode == 8 || mode == 9) && target == 0x8893))) {
1767
+ gWebGL.glBufferData(target, size, bytes, 0x88E0); // STREAM_DRAW selects ANGLE dynamic storage.
1768
+ } else if (eligible && mode == 2) {
1769
+ gWebGL.glBufferData(target, size, bytes, found->second.usage);
1770
+ } else {
1771
+ gWebGL.glBufferSubData(target, offset, size, bytes);
1772
+ }
1773
+ #if defined(GEA_PROFILE_ALLOCATIONS) || defined(GEA_PROFILE_NATIVE_FRAMES)
1774
+ // Fixed storage keeps the profiler from contributing per-upload heap work.
1775
+ struct Row { GLuint buffer = 0; GLsizeiptr size = 0; GLintptr offset = 0; GLenum target = 0, usage = 0; unsigned calls = 0, full = 0; double ms = 0, maximum = 0; };
1776
+ static Row rows[32];
1777
+ static auto window = start;
1778
+ const double elapsed = std::chrono::duration<double, std::milli>(ProfileClock::now() - start).count();
1779
+ for (auto& row : rows) {
1780
+ if (row.calls && (row.buffer != buffer || row.size != size || row.offset != offset || row.target != target)) continue;
1781
+ row.buffer = buffer; row.size = size; row.offset = offset; row.target = target;
1782
+ row.usage = found != gBuffers.end() ? found->second.usage : 0;
1783
+ ++row.calls; row.full += full; row.ms += elapsed; row.maximum = std::max(row.maximum, elapsed);
1784
+ break;
1785
+ }
1786
+ if (std::chrono::duration<double>(start - window).count() >= 1.0) {
1787
+ for (auto& row : rows) {
1788
+ if (row.calls) std::fprintf(stderr, "[gea.buffer] mode=%d buffer=%u target=%u usage=%u bytes=%lld offset=%lld calls=%u full=%u ms=%.6f max_ms=%.6f\n",
1789
+ mode, row.buffer, row.target, row.usage, static_cast<long long>(row.size), static_cast<long long>(row.offset), row.calls, row.full, row.ms, row.maximum);
1790
+ row = {};
1791
+ }
1792
+ window = start;
1793
+ }
1794
+ #endif
1795
+ }
1796
+
1797
+ // Streaming experiment ends here.
1798
+ #else
1799
+ static void bindNativeBuffer(GLenum target, GLuint handle) { gWebGL.glBindBuffer(target, handle); }
1800
+ static void deleteNativeBuffer(GLuint handle) { gWebGL.glDeleteBuffers(1, &handle); }
1801
+ static void nativeBufferData(GLenum target, GLsizeiptr size, const void* bytes, GLenum usage) { gWebGL.glBufferData(target, size, bytes, usage); }
1802
+ static void nativeBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* bytes) { gWebGL.glBufferSubData(target, offset, size, bytes); }
1803
+ #endif
1804
+
1805
+ extern "C" double gea_three_webgl_create_buffer() {
1806
+ geaTrapCheck("gea_three_webgl_create_buffer"); GEA_WEBGL_DOUBLE("createBuffer"); return generatedBuffer(); }
1807
+ extern "C" void gea_three_webgl_delete_buffer(double buffer) {
1808
+ geaTrapCheck("gea_three_webgl_delete_buffer"); GEA_WEBGL_VOID("deleteBuffer"); deleteNativeBuffer(asGLuint(buffer)); }
1809
+ extern "C" void gea_three_webgl_bind_buffer(double target, double buffer) {
1810
+ geaTrapCheck("gea_three_webgl_bind_buffer"); GEA_WEBGL_VOID("bindBuffer"); bindNativeBuffer(asGLenum(target), asGLuint(buffer)); }
1811
+
1812
+ extern "C" double gea_three_webgl_create_shader(double type) {
1813
+ geaTrapCheck("gea_three_webgl_create_shader"); GEA_WEBGL_DOUBLE("createShader"); return gWebGL.glCreateShader(asGLenum(type)); }
1814
+ extern "C" void gea_three_webgl_shader_source(double shader, std::string source) {
1815
+ geaTrapCheck("gea_three_webgl_shader_source");
1816
+ GEA_WEBGL_VOID("shaderSource");
1817
+ const char *sourceBytes = source.c_str();
1818
+ const GLint length = static_cast<GLint>(source.size());
1819
+ gWebGL.glShaderSource(asGLuint(shader), 1, &sourceBytes, &length);
1820
+ }
1821
+ extern "C" void gea_three_webgl_compile_shader(double shader) {
1822
+ geaTrapCheck("gea_three_webgl_compile_shader");
1823
+ GEA_WEBGL_VOID("compileShader");
1824
+ gWebGL.glCompileShader(asGLuint(shader));
1825
+ }
1826
+ extern "C" double gea_three_webgl_get_shader_parameter(double shader, double pname) {
1827
+ geaTrapCheck("gea_three_webgl_get_shader_parameter");
1828
+ GEA_WEBGL_DOUBLE("getShaderParameter");
1829
+ GLint value = 0;
1830
+ gWebGL.glGetShaderiv(asGLuint(shader), asGLenum(pname), &value);
1831
+ return value;
1832
+ }
1833
+ extern "C" void gea_three_webgl_delete_shader(double shader) {
1834
+ geaTrapCheck("gea_three_webgl_delete_shader"); GEA_WEBGL_VOID("deleteShader"); gWebGL.glDeleteShader(asGLuint(shader)); }
1835
+
1836
+ extern "C" double gea_three_webgl_create_program() {
1837
+ geaTrapCheck("gea_three_webgl_create_program"); GEA_WEBGL_DOUBLE("createProgram"); return gWebGL.glCreateProgram(); }
1838
+ extern "C" void gea_three_webgl_attach_shader(double program, double shader) {
1839
+ geaTrapCheck("gea_three_webgl_attach_shader"); GEA_WEBGL_VOID("attachShader"); gWebGL.glAttachShader(asGLuint(program), asGLuint(shader)); }
1840
+ extern "C" void gea_three_webgl_link_program(double program) {
1841
+ geaTrapCheck("gea_three_webgl_link_program");
1842
+ GEA_WEBGL_VOID("linkProgram");
1843
+ gWebGL.glLinkProgram(asGLuint(program));
1844
+ }
1845
+ extern "C" double gea_three_webgl_get_program_parameter(double program, double pname) {
1846
+ geaTrapCheck("gea_three_webgl_get_program_parameter");
1847
+ GEA_WEBGL_DOUBLE("getProgramParameter");
1848
+ GLint value = 0;
1849
+ gWebGL.glGetProgramiv(asGLuint(program), asGLenum(pname), &value);
1850
+ return value;
1851
+ }
1852
+ extern "C" void gea_three_webgl_delete_program(double program) {
1853
+ geaTrapCheck("gea_three_webgl_delete_program"); GEA_WEBGL_VOID("deleteProgram"); gWebGL.glDeleteProgram(asGLuint(program)); }
1854
+ extern "C" void gea_three_webgl_use_program(double program) {
1855
+ geaTrapCheck("gea_three_webgl_use_program"); GEA_WEBGL_VOID("useProgram");
1856
+ if (nativeWebGLTrapEnabled()) {
1857
+ static int logged = 0;
1858
+ if (logged++ < 40) smokeLog("[gea-webgl-trap] useProgram(%f -> %u)", program, asGLuint(program));
1859
+ }
1860
+ gWebGL.glUseProgram(asGLuint(program)); }
1861
+ extern "C" double gea_three_webgl_get_attrib_location(double program, std::string name) {
1862
+ geaTrapCheck("gea_three_webgl_get_attrib_location");
1863
+ GEA_WEBGL_DOUBLE("getAttribLocation");
1864
+ return gWebGL.glGetAttribLocation(asGLuint(program), name.c_str());
1865
+ }
1866
+ extern "C" double gea_three_webgl_get_uniform_location(double program, std::string name) {
1867
+ geaTrapCheck("gea_three_webgl_get_uniform_location");
1868
+ GEA_WEBGL_DOUBLE("getUniformLocation");
1869
+ const GLint loc = gWebGL.glGetUniformLocation(asGLuint(program), name.c_str());
1870
+ if (nativeWebGLTrapEnabled()) {
1871
+ static int logged = 0;
1872
+ if (logged++ < 120) {
1873
+ smokeLog("[gea-webgl-trap] getUniformLocation(prog=%u, %s) = %d", asGLuint(program), name.c_str(), loc);
1874
+ }
1875
+ }
1876
+ return loc;
1877
+ }
1878
+
1879
+ // Active-uniform/attrib reflection, serialized as "name|type|size" (empty
1880
+ // string past the end). This is REAL driver reflection via ANGLE — the TS shim
1881
+ // must NOT reconstruct it by text-parsing GLSL: three.js relies on
1882
+ // glGetActiveUniform semantics (struct arrays expanded per member as
1883
+ // "lights[0].member", basic arrays reported as "name[0]" with size=N) to build
1884
+ // its uniform upload tree; a source-text parser misses #define-sized arrays
1885
+ // and struct expansion, which silently kills all struct uniforms (lights).
1886
+ static std::string geaActiveInfoString(bool uniform, double program, double index) {
1887
+ GLchar name[512] = {0};
1888
+ GLsizei length = 0;
1889
+ GLint size = 0;
1890
+ GLenum type = 0;
1891
+ if (uniform) {
1892
+ if (!gWebGL.glGetActiveUniform) return std::string();
1893
+ gWebGL.glGetActiveUniform(asGLuint(program), asGLuint(index), sizeof name, &length, &size, &type, name);
1894
+ } else {
1895
+ if (!gWebGL.glGetActiveAttrib) return std::string();
1896
+ gWebGL.glGetActiveAttrib(asGLuint(program), asGLuint(index), sizeof name, &length, &size, &type, name);
1897
+ }
1898
+ if (length <= 0) return std::string();
1899
+ std::string out(name, static_cast<size_t>(length));
1900
+ out += "|";
1901
+ out += std::to_string(static_cast<unsigned>(type));
1902
+ out += "|";
1903
+ out += std::to_string(size);
1904
+ if (nativeWebGLTrapEnabled()) {
1905
+ static int logged = 0;
1906
+ if (logged++ < 240) {
1907
+ smokeLog("[gea-webgl-trap] active%s(prog=%u, index=%u, name=%s, type=%u, size=%d)",
1908
+ uniform ? "Uniform" : "Attrib", asGLuint(program), asGLuint(index), name,
1909
+ static_cast<unsigned>(type), size);
1910
+ }
1911
+ }
1912
+ return out;
1913
+ }
1914
+ extern "C" std::string gea_three_webgl_get_active_uniform_info(double program, double index) {
1915
+ geaTrapCheck("gea_three_webgl_get_active_uniform_info");
1916
+ if (!ensureWebGLCurrent("getActiveUniform")) return std::string();
1917
+ return geaActiveInfoString(true, program, index);
1918
+ }
1919
+ extern "C" std::string gea_three_webgl_get_active_attrib_info(double program, double index) {
1920
+ geaTrapCheck("gea_three_webgl_get_active_attrib_info");
1921
+ if (!ensureWebGLCurrent("getActiveAttrib")) return std::string();
1922
+ return geaActiveInfoString(false, program, index);
1923
+ }
1924
+
1925
+ extern "C" void gea_three_webgl_enable_vertex_attrib_array(double index) {
1926
+ geaTrapCheck("gea_three_webgl_enable_vertex_attrib_array"); GEA_WEBGL_VOID("enableVertexAttribArray"); gWebGL.glEnableVertexAttribArray(asGLuint(index)); }
1927
+ extern "C" void gea_three_webgl_disable_vertex_attrib_array(double index) {
1928
+ geaTrapCheck("gea_three_webgl_disable_vertex_attrib_array"); GEA_WEBGL_VOID("disableVertexAttribArray"); gWebGL.glDisableVertexAttribArray(asGLuint(index)); }
1929
+ extern "C" void gea_three_webgl_vertex_attrib_pointer(double index, double size, double type, double normalized, double stride, double offset) {
1930
+ geaTrapCheck("gea_three_webgl_vertex_attrib_pointer");
1931
+ GEA_WEBGL_VOID("vertexAttribPointer");
1932
+ gWebGL.glVertexAttribPointer(
1933
+ asGLuint(index),
1934
+ asGLint(size),
1935
+ asGLenum(type),
1936
+ normalized != 0.0,
1937
+ asGLsizei(stride),
1938
+ reinterpret_cast<const void *>(static_cast<std::uintptr_t>(offset))
1939
+ );
1940
+ }
1941
+
1942
+ extern "C" void gea_three_webgl_uniform_1f(double location, double x) {
1943
+ geaTrapCheck("gea_three_webgl_uniform_1f"); GEA_WEBGL_VOID("uniform1f");
1944
+ gWebGL.glUniform1f(asGLint(location), static_cast<GLfloat>(x)); }
1945
+ extern "C" void gea_three_webgl_uniform_1i(double location, double x) {
1946
+ geaTrapCheck("gea_three_webgl_uniform_1i"); GEA_WEBGL_VOID("uniform1i");
1947
+ gWebGL.glUniform1i(asGLint(location), asGLint(x)); }
1948
+ extern "C" void gea_three_webgl_uniform_1ui(double location, double x) {
1949
+ geaTrapCheck("gea_three_webgl_uniform_1ui"); GEA_WEBGL_VOID("uniform1ui"); gWebGL.glUniform1ui(asGLint(location), asGLuint(x)); }
1950
+ extern "C" void gea_three_webgl_uniform_2f(double location, double x, double y) {
1951
+ geaTrapCheck("gea_three_webgl_uniform_2f"); GEA_WEBGL_VOID("uniform2f");
1952
+ gWebGL.glUniform2f(asGLint(location), static_cast<GLfloat>(x), static_cast<GLfloat>(y)); }
1953
+ extern "C" void gea_three_webgl_uniform_2i(double location, double x, double y) {
1954
+ geaTrapCheck("gea_three_webgl_uniform_2i"); GEA_WEBGL_VOID("uniform2i"); gWebGL.glUniform2i(asGLint(location), asGLint(x), asGLint(y)); }
1955
+ extern "C" void gea_three_webgl_uniform_2ui(double location, double x, double y) {
1956
+ geaTrapCheck("gea_three_webgl_uniform_2ui"); GEA_WEBGL_VOID("uniform2ui"); gWebGL.glUniform2ui(asGLint(location), asGLuint(x), asGLuint(y)); }
1957
+ extern "C" void gea_three_webgl_uniform_3f(double location, double x, double y, double z) {
1958
+ geaTrapCheck("gea_three_webgl_uniform_3f"); GEA_WEBGL_VOID("uniform3f");
1959
+ gWebGL.glUniform3f(asGLint(location), static_cast<GLfloat>(x), static_cast<GLfloat>(y), static_cast<GLfloat>(z)); }
1960
+ extern "C" void gea_three_webgl_uniform_3i(double location, double x, double y, double z) {
1961
+ geaTrapCheck("gea_three_webgl_uniform_3i"); GEA_WEBGL_VOID("uniform3i"); gWebGL.glUniform3i(asGLint(location), asGLint(x), asGLint(y), asGLint(z)); }
1962
+ extern "C" void gea_three_webgl_uniform_3ui(double location, double x, double y, double z) {
1963
+ geaTrapCheck("gea_three_webgl_uniform_3ui"); GEA_WEBGL_VOID("uniform3ui"); gWebGL.glUniform3ui(asGLint(location), asGLuint(x), asGLuint(y), asGLuint(z)); }
1964
+ extern "C" void gea_three_webgl_uniform_4f(double location, double x, double y, double z, double w) {
1965
+ geaTrapCheck("gea_three_webgl_uniform_4f"); GEA_WEBGL_VOID("uniform4f"); gWebGL.glUniform4f(asGLint(location), static_cast<GLfloat>(x), static_cast<GLfloat>(y), static_cast<GLfloat>(z), static_cast<GLfloat>(w)); }
1966
+ extern "C" void gea_three_webgl_uniform_4i(double location, double x, double y, double z, double w) {
1967
+ geaTrapCheck("gea_three_webgl_uniform_4i"); GEA_WEBGL_VOID("uniform4i"); gWebGL.glUniform4i(asGLint(location), asGLint(x), asGLint(y), asGLint(z), asGLint(w)); }
1968
+ extern "C" void gea_three_webgl_uniform_4ui(double location, double x, double y, double z, double w) {
1969
+ geaTrapCheck("gea_three_webgl_uniform_4ui"); GEA_WEBGL_VOID("uniform4ui"); gWebGL.glUniform4ui(asGLint(location), asGLuint(x), asGLuint(y), asGLuint(z), asGLuint(w)); }
1970
+
1971
+ extern "C" double gea_three_webgl_create_texture() {
1972
+ geaTrapCheck("gea_three_webgl_create_texture"); GEA_WEBGL_DOUBLE("createTexture"); return generatedTexture(); }
1973
+ extern "C" void gea_three_webgl_delete_texture(double texture) {
1974
+ geaTrapCheck("gea_three_webgl_delete_texture"); GEA_WEBGL_VOID("deleteTexture"); GLuint handle = asGLuint(texture); gWebGL.glDeleteTextures(1, &handle); }
1975
+ extern "C" void gea_three_webgl_bind_texture(double target, double texture) {
1976
+ geaTrapCheck("gea_three_webgl_bind_texture"); GEA_WEBGL_VOID("bindTexture"); gWebGL.glBindTexture(asGLenum(target), asGLuint(texture)); }
1977
+ extern "C" void gea_three_webgl_active_texture(double texture) {
1978
+ geaTrapCheck("gea_three_webgl_active_texture"); GEA_WEBGL_VOID("activeTexture"); gWebGL.glActiveTexture(asGLenum(texture)); }
1979
+ extern "C" void gea_three_webgl_tex_parameteri(double target, double pname, double param) {
1980
+ geaTrapCheck("gea_three_webgl_tex_parameteri"); GEA_WEBGL_VOID("texParameteri");
1981
+ gWebGL.glTexParameteri(asGLenum(target), asGLenum(pname), asGLint(param));
1982
+ }
1983
+ extern "C" void gea_three_webgl_pixel_storei(double pname, double param) {
1984
+ geaTrapCheck("gea_three_webgl_pixel_storei"); GEA_WEBGL_VOID("pixelStorei");
1985
+ const GLenum glPname = asGLenum(pname);
1986
+ // UNPACK_FLIP_Y_WEBGL (0x9240), UNPACK_PREMULTIPLY_ALPHA_WEBGL (0x9241) and
1987
+ // UNPACK_COLORSPACE_CONVERSION_WEBGL (0x9243) are WebGL-only client-side
1988
+ // pixel-store parameters: a browser's WebGL layer consumes them (applying any
1989
+ // flip/premultiply in software during texImage) and never forwards them to
1990
+ // GL. ANGLE ES3 has no such enums and rejects them with GL_INVALID_ENUM —
1991
+ // three.js sets all three on every texture upload, which is the source of the
1992
+ // frame-0 0x0500 seen at startup (later tex_parameteri/tex_storage_2d calls
1993
+ // only observe that sticky error). This host performs no GL-side flip either
1994
+ // way, so dropping these to match browser semantics is behaviour-preserving
1995
+ // and keeps the GL error state clean.
1996
+ if (glPname == 0x9240 || glPname == 0x9241 || glPname == 0x9243) return;
1997
+ gWebGL.glPixelStorei(glPname, asGLint(param)); }
1998
+ extern "C" void gea_three_webgl_generate_mipmap(double target) {
1999
+ geaTrapCheck("gea_three_webgl_generate_mipmap"); GEA_WEBGL_VOID("generateMipmap"); gWebGL.glGenerateMipmap(asGLenum(target)); }
2000
+
2001
+ extern "C" double gea_three_webgl_create_vertex_array() {
2002
+ geaTrapCheck("gea_three_webgl_create_vertex_array"); GEA_WEBGL_DOUBLE("createVertexArray"); return generatedVertexArray(); }
2003
+ extern "C" void gea_three_webgl_bind_vertex_array(double vertexArray) {
2004
+ geaTrapCheck("gea_three_webgl_bind_vertex_array"); GEA_WEBGL_VOID("bindVertexArray"); gWebGL.glBindVertexArray(asGLuint(vertexArray)); }
2005
+ extern "C" void gea_three_webgl_delete_vertex_array(double vertexArray) {
2006
+ geaTrapCheck("gea_three_webgl_delete_vertex_array"); GEA_WEBGL_VOID("deleteVertexArray"); GLuint handle = asGLuint(vertexArray); gWebGL.glDeleteVertexArrays(1, &handle); }
2007
+
2008
+ extern "C" double gea_three_webgl_create_framebuffer() {
2009
+ geaTrapCheck("gea_three_webgl_create_framebuffer"); GEA_WEBGL_DOUBLE("createFramebuffer"); return generatedFramebuffer(); }
2010
+ extern "C" void gea_three_webgl_bind_framebuffer(double target, double framebuffer) {
2011
+ geaTrapCheck("gea_three_webgl_bind_framebuffer"); GEA_WEBGL_VOID("bindFramebuffer");
2012
+ if (nativeShadowColorAuditEnabled()) {
2013
+ static int bindLogs = 0;
2014
+ if (bindLogs++ < 80) smokeLog("[shadow-color-state] bindFramebuffer target=0x%04x fbo=%u", asGLenum(target), asGLuint(framebuffer));
2015
+ }
2016
+ if (nativeShadowColorAuditEnabled() && gWebGL.glReadPixels && gWebGL.glGetIntegerv && gWebGL.glGetError) {
2017
+ static GLuint previousFramebuffer = 0;
2018
+ static bool auditedShadowTarget = false;
2019
+ const GLuint nextFramebuffer = asGLuint(framebuffer);
2020
+ if (previousFramebuffer != 0 && nextFramebuffer == 0 && !auditedShadowTarget) {
2021
+ GLint viewport[4] = {0, 0, 0, 0};
2022
+ gWebGL.glGetIntegerv(0x0BA2 /* GL_VIEWPORT */, viewport);
2023
+ if (viewport[2] == 2048 && viewport[3] == 2048) {
2024
+ const size_t pixelCount = static_cast<size_t>(viewport[2]) * static_cast<size_t>(viewport[3]);
2025
+ std::vector<uint8_t> rgba(pixelCount * 4);
2026
+ while (gWebGL.glGetError() != 0) {}
2027
+ gWebGL.glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3],
2028
+ 0x1908 /* GL_RGBA */, 0x1401 /* GL_UNSIGNED_BYTE */, rgba.data());
2029
+ size_t nonWhite = 0;
2030
+ uint8_t minimum = 255;
2031
+ uint8_t maximum = 0;
2032
+ for (size_t i = 0; i < pixelCount; ++i) {
2033
+ const uint8_t r = rgba[i * 4];
2034
+ const uint8_t g = rgba[i * 4 + 1];
2035
+ const uint8_t b = rgba[i * 4 + 2];
2036
+ minimum = std::min(minimum, std::min(r, std::min(g, b)));
2037
+ maximum = std::max(maximum, std::max(r, std::max(g, b)));
2038
+ if (r != 255 || g != 255 || b != 255) ++nonWhite;
2039
+ }
2040
+ const GLenum error = gWebGL.glGetError();
2041
+ smokeLog("[shadow-color-audit] fbo=%u viewport=%d,%d %dx%d nonWhite=%zu/%zu min=%u max=%u error=0x%04x",
2042
+ previousFramebuffer, viewport[0], viewport[1], viewport[2], viewport[3], nonWhite, pixelCount,
2043
+ static_cast<unsigned>(minimum), static_cast<unsigned>(maximum), error);
2044
+ auditedShadowTarget = true;
2045
+ }
2046
+ }
2047
+ previousFramebuffer = nextFramebuffer;
2048
+ }
2049
+ gWebGL.glBindFramebuffer(asGLenum(target), asGLuint(framebuffer)); }
2050
+ extern "C" void gea_three_webgl_delete_framebuffer(double framebuffer) {
2051
+ geaTrapCheck("gea_three_webgl_delete_framebuffer"); GEA_WEBGL_VOID("deleteFramebuffer"); GLuint handle = asGLuint(framebuffer); gWebGL.glDeleteFramebuffers(1, &handle); }
2052
+ extern "C" void gea_three_webgl_framebuffer_texture_2d(double target, double attachment, double textarget, double texture, double level) {
2053
+ geaTrapCheck("gea_three_webgl_framebuffer_texture_2d"); GEA_WEBGL_VOID("framebufferTexture2D");
2054
+ gWebGL.glFramebufferTexture2D(asGLenum(target), asGLenum(attachment), asGLenum(textarget), asGLuint(texture), asGLint(level)); }
2055
+ extern "C" double gea_three_webgl_check_framebuffer_status(double target) {
2056
+ geaTrapCheck("gea_three_webgl_check_framebuffer_status"); GEA_WEBGL_DOUBLE("checkFramebufferStatus"); return gWebGL.glCheckFramebufferStatus(asGLenum(target)); }
2057
+ extern "C" double gea_three_webgl_create_renderbuffer() {
2058
+ geaTrapCheck("gea_three_webgl_create_renderbuffer"); GEA_WEBGL_DOUBLE("createRenderbuffer"); return generatedRenderbuffer(); }
2059
+ extern "C" void gea_three_webgl_bind_renderbuffer(double target, double renderbuffer) {
2060
+ geaTrapCheck("gea_three_webgl_bind_renderbuffer"); GEA_WEBGL_VOID("bindRenderbuffer"); gWebGL.glBindRenderbuffer(asGLenum(target), asGLuint(renderbuffer)); }
2061
+ extern "C" void gea_three_webgl_delete_renderbuffer(double renderbuffer) {
2062
+ geaTrapCheck("gea_three_webgl_delete_renderbuffer"); GEA_WEBGL_VOID("deleteRenderbuffer"); GLuint handle = asGLuint(renderbuffer); gWebGL.glDeleteRenderbuffers(1, &handle); }
2063
+ extern "C" void gea_three_webgl_renderbuffer_storage(double target, double internalFormat, double width, double height) {
2064
+ geaTrapCheck("gea_three_webgl_renderbuffer_storage"); GEA_WEBGL_VOID("renderbufferStorage"); gWebGL.glRenderbufferStorage(asGLenum(target), asGLenum(internalFormat), asGLsizei(width), asGLsizei(height)); }
2065
+ extern "C" void gea_three_webgl_framebuffer_renderbuffer(double target, double attachment, double renderbuffertarget, double renderbuffer) {
2066
+ geaTrapCheck("gea_three_webgl_framebuffer_renderbuffer"); GEA_WEBGL_VOID("framebufferRenderbuffer"); gWebGL.glFramebufferRenderbuffer(asGLenum(target), asGLenum(attachment), asGLenum(renderbuffertarget), asGLuint(renderbuffer)); }
2067
+ extern "C" void gea_three_webgl_draw_buffers(double attachment) {
2068
+ geaTrapCheck("gea_three_webgl_draw_buffers"); GEA_WEBGL_VOID("drawBuffers"); const GLenum glAttachment = asGLenum(attachment); gWebGL.glDrawBuffers(1, &glAttachment); }
2069
+
2070
+ extern "C" void gea_three_webgl_clear_color(double r, double g, double b, double a) {
2071
+ geaTrapCheck("gea_three_webgl_clear_color"); GEA_WEBGL_VOID("clearColor");
2072
+ if (nativeShadowColorAuditEnabled()) {
2073
+ static int clearColorLogs = 0;
2074
+ if (clearColorLogs++ < 20) smokeLog("[shadow-color-state] clearColor %.3f %.3f %.3f %.3f", r, g, b, a);
2075
+ }
2076
+ gWebGL.glClearColor(static_cast<GLfloat>(r), static_cast<GLfloat>(g), static_cast<GLfloat>(b), static_cast<GLfloat>(a)); }
2077
+ extern "C" void gea_three_webgl_clear(double mask) {
2078
+ geaTrapCheck("gea_three_webgl_clear"); GEA_WEBGL_VOID("clear");
2079
+ if (nativeShadowColorAuditEnabled() && gWebGL.glGetIntegerv) {
2080
+ static int clearLogs = 0;
2081
+ if (clearLogs++ < 20) {
2082
+ GLint framebuffer = 0;
2083
+ gWebGL.glGetIntegerv(0x8CA6 /* GL_FRAMEBUFFER_BINDING */, &framebuffer);
2084
+ smokeLog("[shadow-color-state] clear fbo=%d mask=0x%04x", framebuffer, asGLbitfield(mask));
2085
+ }
2086
+ }
2087
+ gWebGL.glClear(asGLbitfield(mask)); }
2088
+ extern "C" void gea_three_webgl_clear_depth(double depth) {
2089
+ geaTrapCheck("gea_three_webgl_clear_depth"); GEA_WEBGL_VOID("clearDepth"); gWebGL.glClearDepthf(static_cast<GLfloat>(depth)); }
2090
+ extern "C" void gea_three_webgl_clear_stencil(double stencil) {
2091
+ geaTrapCheck("gea_three_webgl_clear_stencil"); GEA_WEBGL_VOID("clearStencil"); gWebGL.glClearStencil(asGLint(stencil)); }
2092
+ extern "C" void gea_three_webgl_color_mask(double r, double g, double b, double a) {
2093
+ geaTrapCheck("gea_three_webgl_color_mask"); GEA_WEBGL_VOID("colorMask"); gWebGL.glColorMask(r != 0.0, g != 0.0, b != 0.0, a != 0.0); }
2094
+ extern "C" void gea_three_webgl_depth_mask(double flag) {
2095
+ geaTrapCheck("gea_three_webgl_depth_mask"); GEA_WEBGL_VOID("depthMask"); gWebGL.glDepthMask(flag != 0.0 ? GL_TRUE_VALUE : GL_FALSE_VALUE); }
2096
+ extern "C" void gea_three_webgl_depth_func(double func) {
2097
+ geaTrapCheck("gea_three_webgl_depth_func"); GEA_WEBGL_VOID("depthFunc"); gWebGL.glDepthFunc(asGLenum(func)); }
2098
+ extern "C" void gea_three_webgl_enable(double cap) {
2099
+ geaTrapCheck("gea_three_webgl_enable"); GEA_WEBGL_VOID("enable");
2100
+ if (nativeBlendDebugEnabled() && asGLenum(cap) == 0x0BE2) {
2101
+ smokeLog("[blend-dbg] enable BLEND");
2102
+ }
2103
+ gWebGL.glEnable(asGLenum(cap)); }
2104
+ extern "C" void gea_three_webgl_disable(double cap) {
2105
+ geaTrapCheck("gea_three_webgl_disable"); GEA_WEBGL_VOID("disable");
2106
+ if (nativeBlendDebugEnabled() && asGLenum(cap) == 0x0BE2) {
2107
+ smokeLog("[blend-dbg] disable BLEND");
2108
+ }
2109
+ gWebGL.glDisable(asGLenum(cap)); }
2110
+ extern "C" void gea_three_webgl_blend_func(double sfactor, double dfactor) {
2111
+ geaTrapCheck("gea_three_webgl_blend_func"); GEA_WEBGL_VOID("blendFunc");
2112
+ if (nativeBlendDebugEnabled()) {
2113
+ smokeLog("[blend-dbg] blendFunc src=0x%04x dst=0x%04x", asGLenum(sfactor), asGLenum(dfactor));
2114
+ }
2115
+ gWebGL.glBlendFunc(asGLenum(sfactor), asGLenum(dfactor)); }
2116
+ extern "C" void gea_three_webgl_blend_func_separate(double srcRGB, double dstRGB, double srcAlpha, double dstAlpha) {
2117
+ geaTrapCheck("gea_three_webgl_blend_func_separate"); GEA_WEBGL_VOID("blendFuncSeparate");
2118
+ if (nativeBlendDebugEnabled()) {
2119
+ smokeLog("[blend-dbg] blendFuncSeparate rgb=(0x%04x,0x%04x) alpha=(0x%04x,0x%04x)",
2120
+ asGLenum(srcRGB), asGLenum(dstRGB), asGLenum(srcAlpha), asGLenum(dstAlpha));
2121
+ }
2122
+ gWebGL.glBlendFuncSeparate(asGLenum(srcRGB), asGLenum(dstRGB), asGLenum(srcAlpha), asGLenum(dstAlpha)); }
2123
+ extern "C" void gea_three_webgl_blend_equation(double mode) {
2124
+ geaTrapCheck("gea_three_webgl_blend_equation"); GEA_WEBGL_VOID("blendEquation");
2125
+ if (nativeBlendDebugEnabled()) {
2126
+ smokeLog("[blend-dbg] blendEquation mode=0x%04x", asGLenum(mode));
2127
+ }
2128
+ gWebGL.glBlendEquation(asGLenum(mode)); }
2129
+ extern "C" void gea_three_webgl_blend_equation_separate(double modeRGB, double modeAlpha) {
2130
+ geaTrapCheck("gea_three_webgl_blend_equation_separate"); GEA_WEBGL_VOID("blendEquationSeparate");
2131
+ if (nativeBlendDebugEnabled()) {
2132
+ smokeLog("[blend-dbg] blendEquationSeparate rgb=0x%04x alpha=0x%04x", asGLenum(modeRGB), asGLenum(modeAlpha));
2133
+ }
2134
+ gWebGL.glBlendEquationSeparate(asGLenum(modeRGB), asGLenum(modeAlpha)); }
2135
+ extern "C" void gea_three_webgl_cull_face(double mode) {
2136
+ geaTrapCheck("gea_three_webgl_cull_face"); GEA_WEBGL_VOID("cullFace"); gWebGL.glCullFace(asGLenum(mode)); }
2137
+ extern "C" void gea_three_webgl_front_face(double mode) {
2138
+ geaTrapCheck("gea_three_webgl_front_face"); GEA_WEBGL_VOID("frontFace"); gWebGL.glFrontFace(asGLenum(mode)); }
2139
+ extern "C" void gea_three_webgl_viewport(double x, double y, double width, double height) {
2140
+ geaTrapCheck("gea_three_webgl_viewport"); GEA_WEBGL_VOID("viewport"); gWebGL.glViewport(asGLint(x), asGLint(y), asGLsizei(width), asGLsizei(height)); }
2141
+ extern "C" void gea_three_webgl_scissor(double x, double y, double width, double height) {
2142
+ geaTrapCheck("gea_three_webgl_scissor"); GEA_WEBGL_VOID("scissor"); gWebGL.glScissor(asGLint(x), asGLint(y), asGLsizei(width), asGLsizei(height)); }
2143
+ extern "C" void gea_three_webgl_line_width(double width) {
2144
+ geaTrapCheck("gea_three_webgl_line_width"); GEA_WEBGL_VOID("lineWidth"); gWebGL.glLineWidth(static_cast<GLfloat>(width)); }
2145
+ extern "C" void gea_three_webgl_polygon_offset(double factor, double units) {
2146
+ geaTrapCheck("gea_three_webgl_polygon_offset"); GEA_WEBGL_VOID("polygonOffset"); gWebGL.glPolygonOffset(static_cast<GLfloat>(factor), static_cast<GLfloat>(units)); }
2147
+ extern "C" void gea_three_webgl_stencil_mask(double mask) {
2148
+ geaTrapCheck("gea_three_webgl_stencil_mask"); GEA_WEBGL_VOID("stencilMask"); gWebGL.glStencilMask(asGLuint(mask)); }
2149
+ extern "C" void gea_three_webgl_stencil_mask_separate(double face, double mask) {
2150
+ geaTrapCheck("gea_three_webgl_stencil_mask_separate"); GEA_WEBGL_VOID("stencilMaskSeparate"); gWebGL.glStencilMaskSeparate(asGLenum(face), asGLuint(mask)); }
2151
+ extern "C" void gea_three_webgl_stencil_func(double func, double ref, double mask) {
2152
+ geaTrapCheck("gea_three_webgl_stencil_func"); GEA_WEBGL_VOID("stencilFunc"); gWebGL.glStencilFunc(asGLenum(func), asGLint(ref), asGLuint(mask)); }
2153
+ extern "C" void gea_three_webgl_stencil_func_separate(double face, double func, double ref, double mask) {
2154
+ geaTrapCheck("gea_three_webgl_stencil_func_separate"); GEA_WEBGL_VOID("stencilFuncSeparate"); gWebGL.glStencilFuncSeparate(asGLenum(face), asGLenum(func), asGLint(ref), asGLuint(mask)); }
2155
+ extern "C" void gea_three_webgl_stencil_op(double fail, double zfail, double zpass) {
2156
+ geaTrapCheck("gea_three_webgl_stencil_op"); GEA_WEBGL_VOID("stencilOp"); gWebGL.glStencilOp(asGLenum(fail), asGLenum(zfail), asGLenum(zpass)); }
2157
+ extern "C" void gea_three_webgl_stencil_op_separate(double face, double fail, double zfail, double zpass) {
2158
+ geaTrapCheck("gea_three_webgl_stencil_op_separate"); GEA_WEBGL_VOID("stencilOpSeparate"); gWebGL.glStencilOpSeparate(asGLenum(face), asGLenum(fail), asGLenum(zfail), asGLenum(zpass)); }
2159
+ extern "C" void gea_three_webgl_draw_elements(double mode, double count, double type, double offset) {
2160
+ geaTrapCheck("gea_three_webgl_draw_elements"); GEA_WEBGL_VOID("drawElements");
2161
+ gWebGL.glDrawElements(asGLenum(mode), asGLsizei(count), asGLenum(type), reinterpret_cast<const void *>(static_cast<std::uintptr_t>(offset))); }
2162
+ extern "C" void gea_three_webgl_draw_arrays(double mode, double first, double count) {
2163
+ geaTrapCheck("gea_three_webgl_draw_arrays"); GEA_WEBGL_VOID("drawArrays");
2164
+ gWebGL.glDrawArrays(asGLenum(mode), asGLint(first), asGLsizei(count)); }
2165
+ extern "C" void gea_three_webgl_vertex_attrib_divisor(double index, double divisor) {
2166
+ geaTrapCheck("gea_three_webgl_vertex_attrib_divisor"); GEA_WEBGL_VOID("vertexAttribDivisor");
2167
+ gWebGL.glVertexAttribDivisor(asGLuint(index), asGLuint(divisor)); }
2168
+ extern "C" void gea_three_webgl_draw_elements_instanced(double mode, double count, double type, double offset, double instanceCount) {
2169
+ geaTrapCheck("gea_three_webgl_draw_elements_instanced"); GEA_WEBGL_VOID("drawElementsInstanced");
2170
+ gWebGL.glDrawElementsInstanced(asGLenum(mode), asGLsizei(count), asGLenum(type),
2171
+ reinterpret_cast<const void *>(static_cast<std::uintptr_t>(offset)), asGLsizei(instanceCount)); }
2172
+ extern "C" void gea_three_webgl_draw_arrays_instanced(double mode, double first, double count, double instanceCount) {
2173
+ geaTrapCheck("gea_three_webgl_draw_arrays_instanced"); GEA_WEBGL_VOID("drawArraysInstanced");
2174
+ gWebGL.glDrawArraysInstanced(asGLenum(mode), asGLint(first), asGLsizei(count), asGLsizei(instanceCount)); }
2175
+ extern "C" double gea_three_webgl_get_error() {
2176
+ geaTrapCheck("gea_three_webgl_get_error"); GEA_WEBGL_DOUBLE("getError"); return gWebGL.glGetError(); }
2177
+
2178
+ extern "C" double gea_three_webgl_call(
2179
+ double opValue,
2180
+ double a,
2181
+ double b,
2182
+ double c,
2183
+ double d,
2184
+ double e,
2185
+ double f,
2186
+ double h,
2187
+ double i
2188
+ ) {
2189
+ geaTrapCheck("gea_three_webgl_call");
2190
+ if (!gWebGL.ready || !makeCurrent("eglMakeCurrent(call)")) return 0.0;
2191
+ const int op = static_cast<int>(opValue);
2192
+ switch (op) {
2193
+ case OP_CLEAR_COLOR: gWebGL.glClearColor(a, b, c, d); return 0.0;
2194
+ case OP_CLEAR: gWebGL.glClear(static_cast<GLbitfield>(a)); return 0.0;
2195
+ case OP_VIEWPORT: gWebGL.glViewport(a, b, c, d); return 0.0;
2196
+ case OP_ENABLE: gWebGL.glEnable(static_cast<GLenum>(a)); return 0.0;
2197
+ case OP_DISABLE: gWebGL.glDisable(static_cast<GLenum>(a)); return 0.0;
2198
+ case OP_DEPTH_FUNC: gWebGL.glDepthFunc(static_cast<GLenum>(a)); return 0.0;
2199
+ case OP_DEPTH_MASK: gWebGL.glDepthMask(a != 0.0 ? GL_TRUE_VALUE : GL_FALSE_VALUE); return 0.0;
2200
+ case OP_COLOR_MASK: gWebGL.glColorMask(a != 0.0, b != 0.0, c != 0.0, d != 0.0); return 0.0;
2201
+ case OP_BLEND_FUNC: gWebGL.glBlendFunc(static_cast<GLenum>(a), static_cast<GLenum>(b)); return 0.0;
2202
+ case OP_BLEND_FUNC_SEPARATE: gWebGL.glBlendFuncSeparate(a, b, c, d); return 0.0;
2203
+ case OP_BLEND_EQUATION: gWebGL.glBlendEquation(static_cast<GLenum>(a)); return 0.0;
2204
+ case OP_BLEND_EQUATION_SEPARATE: gWebGL.glBlendEquationSeparate(a, b); return 0.0;
2205
+ case OP_CULL_FACE: gWebGL.glCullFace(static_cast<GLenum>(a)); return 0.0;
2206
+ case OP_FRONT_FACE: gWebGL.glFrontFace(static_cast<GLenum>(a)); return 0.0;
2207
+ case OP_CREATE_BUFFER: return generatedBuffer();
2208
+ case OP_DELETE_BUFFER: deleteNativeBuffer(a); return 0.0;
2209
+ case OP_BIND_BUFFER: bindNativeBuffer(a, b); return 0.0;
2210
+ case OP_CREATE_SHADER: return gWebGL.glCreateShader(static_cast<GLenum>(a));
2211
+ case OP_COMPILE_SHADER: gWebGL.glCompileShader(a); return 0.0;
2212
+ case OP_GET_SHADER_PARAMETER: { GLint value = 0; gWebGL.glGetShaderiv(a, b, &value); return value; }
2213
+ case OP_DELETE_SHADER: gWebGL.glDeleteShader(static_cast<GLuint>(a)); return 0.0;
2214
+ case OP_CREATE_PROGRAM: return gWebGL.glCreateProgram();
2215
+ case OP_ATTACH_SHADER: gWebGL.glAttachShader(a, b); return 0.0;
2216
+ case OP_LINK_PROGRAM: gWebGL.glLinkProgram(a); return 0.0;
2217
+ case OP_GET_PROGRAM_PARAMETER: { GLint value = 0; gWebGL.glGetProgramiv(a, b, &value); return value; }
2218
+ case OP_DELETE_PROGRAM: gWebGL.glDeleteProgram(static_cast<GLuint>(a)); return 0.0;
2219
+ case OP_USE_PROGRAM: gWebGL.glUseProgram(a); return 0.0;
2220
+ case OP_ENABLE_VERTEX_ATTRIB_ARRAY: gWebGL.glEnableVertexAttribArray(a); return 0.0;
2221
+ case OP_DISABLE_VERTEX_ATTRIB_ARRAY: gWebGL.glDisableVertexAttribArray(a); return 0.0;
2222
+ case OP_VERTEX_ATTRIB_POINTER: gWebGL.glVertexAttribPointer(a, b, c, d != 0.0, e, reinterpret_cast<const void *>(static_cast<std::uintptr_t>(f))); return 0.0;
2223
+ case OP_UNIFORM_1F: gWebGL.glUniform1f(a, b); return 0.0;
2224
+ case OP_UNIFORM_1I: gWebGL.glUniform1i(a, b); return 0.0;
2225
+ case OP_UNIFORM_2F: gWebGL.glUniform2f(a, b, c); return 0.0;
2226
+ case OP_UNIFORM_3F: gWebGL.glUniform3f(a, b, c, d); return 0.0;
2227
+ case OP_UNIFORM_4F: gWebGL.glUniform4f(a, b, c, d, e); return 0.0;
2228
+ case OP_DRAW_ELEMENTS: gWebGL.glDrawElements(a, b, c, reinterpret_cast<const void *>(static_cast<std::uintptr_t>(d))); return 0.0;
2229
+ case OP_DRAW_ARRAYS: gWebGL.glDrawArrays(a, b, c); return 0.0;
2230
+ case OP_ACTIVE_TEXTURE: gWebGL.glActiveTexture(a); return 0.0;
2231
+ case OP_CREATE_TEXTURE: return generatedTexture();
2232
+ case OP_DELETE_TEXTURE: { GLuint handle = a; gWebGL.glDeleteTextures(1, &handle); return 0.0; }
2233
+ case OP_BIND_TEXTURE: gWebGL.glBindTexture(a, b); return 0.0;
2234
+ case OP_TEX_PARAMETERI: gWebGL.glTexParameteri(a, b, c); return 0.0;
2235
+ case OP_PIXEL_STOREI: gWebGL.glPixelStorei(a, b); return 0.0;
2236
+ case OP_GENERATE_MIPMAP: gWebGL.glGenerateMipmap(a); return 0.0;
2237
+ case OP_CREATE_VERTEX_ARRAY: return generatedVertexArray();
2238
+ case OP_BIND_VERTEX_ARRAY: gWebGL.glBindVertexArray(a); return 0.0;
2239
+ case OP_DELETE_VERTEX_ARRAY: { GLuint handle = a; gWebGL.glDeleteVertexArrays(1, &handle); return 0.0; }
2240
+ case OP_GET_ERROR: return gWebGL.glGetError();
2241
+ case OP_SCISSOR: gWebGL.glScissor(a, b, c, d); return 0.0;
2242
+ case OP_LINE_WIDTH: gWebGL.glLineWidth(a); return 0.0;
2243
+ case OP_POLYGON_OFFSET: gWebGL.glPolygonOffset(a, b); return 0.0;
2244
+ case OP_CLEAR_DEPTH: gWebGL.glClearDepthf(a); return 0.0;
2245
+ case OP_CLEAR_STENCIL: gWebGL.glClearStencil(a); return 0.0;
2246
+ case OP_STENCIL_MASK: gWebGL.glStencilMask(static_cast<GLuint>(a)); return 0.0;
2247
+ case OP_STENCIL_FUNC: gWebGL.glStencilFunc(a, b, c); return 0.0;
2248
+ case OP_STENCIL_OP: gWebGL.glStencilOp(a, b, c); return 0.0;
2249
+ case OP_STENCIL_FUNC_SEPARATE: gWebGL.glStencilFuncSeparate(a, b, c, d); return 0.0;
2250
+ case OP_STENCIL_OP_SEPARATE: gWebGL.glStencilOpSeparate(a, b, c, d); return 0.0;
2251
+ case OP_STENCIL_MASK_SEPARATE: gWebGL.glStencilMaskSeparate(a, static_cast<GLuint>(b)); return 0.0;
2252
+ case OP_CREATE_FRAMEBUFFER: return generatedFramebuffer();
2253
+ case OP_BIND_FRAMEBUFFER: gWebGL.glBindFramebuffer(a, b); return 0.0;
2254
+ case OP_DELETE_FRAMEBUFFER: { GLuint handle = a; gWebGL.glDeleteFramebuffers(1, &handle); return 0.0; }
2255
+ case OP_FRAMEBUFFER_TEXTURE_2D: gWebGL.glFramebufferTexture2D(a, b, c, d, e); return 0.0;
2256
+ case OP_CHECK_FRAMEBUFFER_STATUS: return gWebGL.glCheckFramebufferStatus(a);
2257
+ case OP_CREATE_RENDERBUFFER: return generatedRenderbuffer();
2258
+ case OP_BIND_RENDERBUFFER: gWebGL.glBindRenderbuffer(a, b); return 0.0;
2259
+ case OP_RENDERBUFFER_STORAGE: gWebGL.glRenderbufferStorage(a, b, c, d); return 0.0;
2260
+ case OP_FRAMEBUFFER_RENDERBUFFER: gWebGL.glFramebufferRenderbuffer(a, b, c, d); return 0.0;
2261
+ case OP_DELETE_RENDERBUFFER: { GLuint handle = a; gWebGL.glDeleteRenderbuffers(1, &handle); return 0.0; }
2262
+ case OP_DRAW_BUFFERS: {
2263
+ const GLenum attachment = static_cast<GLenum>(a);
2264
+ gWebGL.glDrawBuffers(1, &attachment);
2265
+ return 0.0;
2266
+ }
2267
+ default: return 0.0;
2268
+ }
2269
+ }
2270
+
2271
+ extern "C" double gea_three_webgl_call_string(
2272
+ double opValue,
2273
+ double a,
2274
+ double b,
2275
+ std::string value
2276
+ ) {
2277
+ geaTrapCheck("gea_three_webgl_call_string");
2278
+ if (!gWebGL.ready || !makeCurrent("eglMakeCurrent(call-string)")) return 0.0;
2279
+ const int op = static_cast<int>(opValue);
2280
+ if (op == OP_SHADER_SOURCE) {
2281
+ const char *source = value.c_str();
2282
+ const GLint length = static_cast<GLint>(value.size());
2283
+ gWebGL.glShaderSource(static_cast<GLuint>(a), 1, &source, &length);
2284
+ return 0.0;
2285
+ }
2286
+ if (op == OP_GET_ATTRIB_LOCATION) {
2287
+ return gWebGL.glGetAttribLocation(static_cast<GLuint>(a), value.c_str());
2288
+ }
2289
+ if (op == OP_GET_UNIFORM_LOCATION) {
2290
+ return gWebGL.glGetUniformLocation(static_cast<GLuint>(a), value.c_str());
2291
+ }
2292
+ return 0.0;
2293
+ }
2294
+
2295
+ // Most uploads here are one vec2/vec3/vec4 or one 3x3/4x4 matrix. Convert
2296
+ // those without an allocator round trip; larger arrays retain owned heap
2297
+ // storage until the synchronous GL upload returns. No shared mutable scratch.
2298
+ class FloatUploadData {
2299
+ float inlineValues_[16];
2300
+ std::vector<float> heapValues_;
2301
+ std::size_t size_;
2302
+ public:
2303
+ explicit FloatUploadData(std::span<const double> values) : size_(values.size()) {
2304
+ if (size_ <= 16) {
2305
+ for (std::size_t i = 0; i < size_; ++i) inlineValues_[i] = static_cast<float>(values[i]);
2306
+ } else {
2307
+ heapValues_.reserve(size_);
2308
+ for (double value : values) heapValues_.push_back(static_cast<float>(value));
2309
+ }
2310
+ }
2311
+ const float* data() const { return size_ <= 16 ? inlineValues_ : heapValues_.data(); }
2312
+ std::size_t size() const { return size_; }
2313
+ bool empty() const { return size_ == 0; }
2314
+ };
2315
+
2316
+ static std::vector<std::uint16_t> asU16Data(std::span<const double> values) {
2317
+ std::vector<std::uint16_t> out;
2318
+ out.reserve(values.size());
2319
+ for (double value : values) out.push_back(static_cast<std::uint16_t>(std::max(0.0f, static_cast<float>(value))));
2320
+ return out;
2321
+ }
2322
+
2323
+ static std::vector<std::uint32_t> asU32Data(std::span<const double> values) {
2324
+ std::vector<std::uint32_t> out;
2325
+ out.reserve(values.size());
2326
+ for (double value : values) out.push_back(static_cast<std::uint32_t>(std::max(0.0f, static_cast<float>(value))));
2327
+ return out;
2328
+ }
2329
+
2330
+ static std::vector<std::uint8_t> asU8Data(std::span<const double> values) {
2331
+ std::vector<std::uint8_t> out;
2332
+ out.reserve(values.size());
2333
+ for (double value : values) out.push_back(static_cast<std::uint8_t>(std::max(0.0f, std::min(255.0f, static_cast<float>(value)))));
2334
+ return out;
2335
+ }
2336
+
2337
+ template <typename Fn>
2338
+ static void withVectorBytes(std::span<const double> values, int typeCode, Fn fn) {
2339
+ if (values.empty()) {
2340
+ fn(nullptr, 0);
2341
+ return;
2342
+ }
2343
+ if (typeCode == 2) {
2344
+ const std::vector<std::uint16_t> data = asU16Data(values);
2345
+ fn(data.data(), static_cast<GLsizeiptr>(data.size() * sizeof(std::uint16_t)));
2346
+ return;
2347
+ }
2348
+ if (typeCode == 3) {
2349
+ const std::vector<std::uint32_t> data = asU32Data(values);
2350
+ fn(data.data(), static_cast<GLsizeiptr>(data.size() * sizeof(std::uint32_t)));
2351
+ return;
2352
+ }
2353
+ if (typeCode == 4) {
2354
+ const std::vector<std::uint8_t> data = asU8Data(values);
2355
+ fn(data.data(), static_cast<GLsizeiptr>(data.size() * sizeof(std::uint8_t)));
2356
+ return;
2357
+ }
2358
+ const FloatUploadData data(values);
2359
+ fn(data.data(), static_cast<GLsizeiptr>(data.size() * sizeof(float)));
2360
+ }
2361
+
2362
+ extern "C" void gea_three_webgl_buffer_data(
2363
+ double target,
2364
+ double usage,
2365
+ double typeCode,
2366
+ std::span<const double> values
2367
+ ) {
2368
+ geaTrapCheck("gea_three_webgl_buffer_data");
2369
+ GEA_WEBGL_VOID("bufferData");
2370
+ withVectorBytes(values, static_cast<int>(typeCode), [&](const void *bytes, GLsizeiptr size) {
2371
+ nativeBufferData(asGLenum(target), size, bytes, asGLenum(usage));
2372
+ });
2373
+ }
2374
+
2375
+ extern "C" void gea_three_webgl_buffer_sub_data(
2376
+ double target,
2377
+ double offset,
2378
+ double typeCode,
2379
+ std::span<const double> values
2380
+ ) {
2381
+ geaTrapCheck("gea_three_webgl_buffer_sub_data");
2382
+ GEA_WEBGL_VOID("bufferSubData");
2383
+ withVectorBytes(values, static_cast<int>(typeCode), [&](const void *bytes, GLsizeiptr size) {
2384
+ nativeBufferSubData(asGLenum(target), static_cast<GLintptr>(offset), size, bytes);
2385
+ });
2386
+ }
2387
+
2388
+ extern "C" void gea_three_webgl_buffer_data_bytes(double target, double usage, const void* bytes, double byteLength) {
2389
+ GEA_WEBGL_VOID("bufferDataBytes");
2390
+ nativeBufferData(asGLenum(target), static_cast<GLsizeiptr>(byteLength), bytes, asGLenum(usage));
2391
+ }
2392
+
2393
+ extern "C" void gea_three_webgl_tex_image_2d_bytes(double target, double level, double internalFormat, double width, double height, double format, double type, const void* bytes, double) {
2394
+ GEA_WEBGL_VOID("texImage2DBytes");
2395
+ gWebGL.glTexImage2D(asGLenum(target), asGLint(level), asGLint(internalFormat), asGLsizei(width), asGLsizei(height), 0, asGLenum(format), asGLenum(type), bytes);
2396
+ }
2397
+
2398
+ extern "C" void gea_three_webgl_tex_sub_image_2d_bytes(double target, double level, double xoffset, double yoffset, double width, double height, double format, double type, const void* bytes, double) {
2399
+ GEA_WEBGL_VOID("texSubImage2DBytes");
2400
+ gWebGL.glTexSubImage2D(asGLenum(target), asGLint(level), asGLint(xoffset), asGLint(yoffset), asGLsizei(width), asGLsizei(height), asGLenum(format), asGLenum(type), bytes);
2401
+ }
2402
+
2403
+ // Native typed arrays already own byte-exact, alias-preserving storage. This
2404
+ // entry point lets generated code upload that storage directly instead of
2405
+ // promoting every element through a temporary std::vector<double> first.
2406
+ extern "C" void gea_three_webgl_buffer_sub_data_bytes(
2407
+ double target,
2408
+ double offset,
2409
+ const void *bytes,
2410
+ double byteLength
2411
+ ) {
2412
+ geaTrapCheck("gea_three_webgl_buffer_sub_data_bytes");
2413
+ GEA_WEBGL_VOID("bufferSubDataBytes");
2414
+ const GLsizeiptr size = static_cast<GLsizeiptr>(std::max(0.0, byteLength));
2415
+ nativeBufferSubData(asGLenum(target), static_cast<GLintptr>(offset), size, bytes);
2416
+ }
2417
+
2418
+ #define GEA_DEFINE_UNIFORM_FV(WIDTH) \
2419
+ extern "C" void gea_three_webgl_uniform_##WIDTH##fv( \
2420
+ double location, const gea::detail::HostNumericArgument<float>& values \
2421
+ ) { \
2422
+ geaTrapCheck("gea_three_webgl_uniform_" #WIDTH "fv"); \
2423
+ GEA_WEBGL_VOID("uniform" #WIDTH "fv"); \
2424
+ const auto& data = values; \
2425
+ const GLsizei count = static_cast<GLsizei>(data.size() / WIDTH); \
2426
+ if (count > 0) gWebGL.glUniform##WIDTH##fv(asGLint(location), count, data.data()); \
2427
+ }
2428
+
2429
+ #define GEA_DEFINE_UNIFORM_IV(WIDTH) \
2430
+ extern "C" void gea_three_webgl_uniform_##WIDTH##iv( \
2431
+ double location, const gea::detail::HostNumericArgument<std::int32_t>& values \
2432
+ ) { \
2433
+ geaTrapCheck("gea_three_webgl_uniform_" #WIDTH "iv"); \
2434
+ GEA_WEBGL_VOID("uniform" #WIDTH "iv"); \
2435
+ const auto& data = values; \
2436
+ const GLsizei count = static_cast<GLsizei>(data.size() / WIDTH); \
2437
+ if (count > 0) gWebGL.glUniform##WIDTH##iv(asGLint(location), count, data.data()); \
2438
+ }
2439
+
2440
+ #define GEA_DEFINE_UNIFORM_UIV(WIDTH) \
2441
+ extern "C" void gea_three_webgl_uniform_##WIDTH##uiv( \
2442
+ double location, const gea::detail::HostNumericArgument<std::uint32_t>& values \
2443
+ ) { \
2444
+ geaTrapCheck("gea_three_webgl_uniform_" #WIDTH "uiv"); \
2445
+ GEA_WEBGL_VOID("uniform" #WIDTH "uiv"); \
2446
+ const auto& data = values; \
2447
+ const GLsizei count = static_cast<GLsizei>(data.size() / WIDTH); \
2448
+ if (count > 0) gWebGL.glUniform##WIDTH##uiv(asGLint(location), count, data.data()); \
2449
+ }
2450
+
2451
+ GEA_DEFINE_UNIFORM_FV(1)
2452
+ GEA_DEFINE_UNIFORM_FV(2)
2453
+ GEA_DEFINE_UNIFORM_FV(3)
2454
+ GEA_DEFINE_UNIFORM_FV(4)
2455
+ GEA_DEFINE_UNIFORM_IV(1)
2456
+ GEA_DEFINE_UNIFORM_IV(2)
2457
+ GEA_DEFINE_UNIFORM_IV(3)
2458
+ GEA_DEFINE_UNIFORM_IV(4)
2459
+ GEA_DEFINE_UNIFORM_UIV(1)
2460
+ GEA_DEFINE_UNIFORM_UIV(2)
2461
+ GEA_DEFINE_UNIFORM_UIV(3)
2462
+ GEA_DEFINE_UNIFORM_UIV(4)
2463
+
2464
+ #undef GEA_DEFINE_UNIFORM_FV
2465
+ #undef GEA_DEFINE_UNIFORM_IV
2466
+ #undef GEA_DEFINE_UNIFORM_UIV
2467
+
2468
+ extern "C" void gea_three_webgl_uniform_matrix_2fv(
2469
+ double location,
2470
+ double count,
2471
+ double transpose,
2472
+ const gea::detail::HostNumericArgument<float>& values
2473
+ ) {
2474
+ geaTrapCheck("gea_three_webgl_uniform_matrix_2fv");
2475
+ GEA_WEBGL_VOID("uniformMatrix2fv");
2476
+ const auto& data = values;
2477
+ if (data.size() % 4 != 0) return;
2478
+ const GLsizei matrixCount = static_cast<GLsizei>(data.size() / 4);
2479
+ if (!data.empty()) gWebGL.glUniformMatrix2fv(asGLint(location), matrixCount, transpose != 0.0, data.data());
2480
+ }
2481
+
2482
+ extern "C" void gea_three_webgl_uniform_matrix_3fv(
2483
+ double location,
2484
+ double count,
2485
+ double transpose,
2486
+ const gea::detail::HostNumericArgument<float>& values
2487
+ ) {
2488
+ geaTrapCheck("gea_three_webgl_uniform_matrix_3fv");
2489
+ GEA_WEBGL_VOID("uniformMatrix3fv");
2490
+ const auto& data = values;
2491
+ if (data.size() % 9 != 0) return;
2492
+ const GLsizei matrixCount = static_cast<GLsizei>(data.size() / 9);
2493
+ if (!data.empty()) gWebGL.glUniformMatrix3fv(asGLint(location), matrixCount, transpose != 0.0, data.data());
2494
+ if (nativeWebGLTrapEnabled()) {
2495
+ static int logged = 0;
2496
+ const GLenum err = gWebGL.glGetError ? gWebGL.glGetError() : 0;
2497
+ if (err != 0 && logged < 24) {
2498
+ ++logged;
2499
+ GLint prog = -1;
2500
+ gWebGL.glGetIntegerv(0x8B8D /* GL_CURRENT_PROGRAM */, &prog);
2501
+ smokeLog("[gea-webgl-trap] uniformMatrix3fv ERR=0x%04x loc=%d count=%d n=%zu prog=%d",
2502
+ err, asGLint(location), asGLsizei(std::max(1.0, count)), values.size(), prog);
2503
+ }
2504
+ }
2505
+ }
2506
+
2507
+ extern "C" void gea_three_webgl_uniform_matrix_4fv(
2508
+ double location,
2509
+ double count,
2510
+ double transpose,
2511
+ const gea::detail::HostNumericArgument<float>& values
2512
+ ) {
2513
+ geaTrapCheck("gea_three_webgl_uniform_matrix_4fv");
2514
+ GEA_WEBGL_VOID("uniformMatrix4fv");
2515
+ const auto& data = values;
2516
+ if (data.size() % 16 != 0) return;
2517
+ const GLsizei matrixCount = static_cast<GLsizei>(data.size() / 16);
2518
+ if (!data.empty()) gWebGL.glUniformMatrix4fv(asGLint(location), matrixCount, transpose != 0.0, data.data());
2519
+ }
2520
+
2521
+ extern "C" void gea_three_webgl_tex_image_2d(
2522
+ double target,
2523
+ double level,
2524
+ double internalFormat,
2525
+ double width,
2526
+ double height,
2527
+ double format,
2528
+ double type,
2529
+ std::span<const double> values
2530
+ ) {
2531
+ geaTrapCheck("gea_three_webgl_tex_image_2d");
2532
+ GEA_WEBGL_VOID("texImage2D");
2533
+ withVectorBytes(values, vectorTypeCodeForGLType(type), [&](const void *bytes, GLsizeiptr) {
2534
+ gWebGL.glTexImage2D(
2535
+ asGLenum(target),
2536
+ asGLint(level),
2537
+ asGLint(internalFormat),
2538
+ asGLsizei(width),
2539
+ asGLsizei(height),
2540
+ 0,
2541
+ asGLenum(format),
2542
+ asGLenum(type),
2543
+ bytes
2544
+ );
2545
+ });
2546
+ }
2547
+
2548
+ extern "C" void gea_three_webgl_tex_storage_2d(
2549
+ double target,
2550
+ double levels,
2551
+ double internalFormat,
2552
+ double width,
2553
+ double height
2554
+ ) {
2555
+ geaTrapCheck("gea_three_webgl_tex_storage_2d");
2556
+ GEA_WEBGL_VOID("texStorage2D");
2557
+ if (nativeWebGLTrapEnabled()) {
2558
+ smokeLog("[gea-webgl-trap] texStorage2D(target=0x%04x levels=%d fmt=0x%04x %dx%d)",
2559
+ asGLenum(target), asGLsizei(std::max(1.0, levels)), asGLenum(internalFormat),
2560
+ asGLsizei(width), asGLsizei(height));
2561
+ }
2562
+ gWebGL.glTexStorage2D(
2563
+ asGLenum(target),
2564
+ asGLsizei(std::max(1.0, levels)),
2565
+ asGLenum(internalFormat),
2566
+ asGLsizei(width),
2567
+ asGLsizei(height)
2568
+ );
2569
+ }
2570
+
2571
+ extern "C" void gea_three_webgl_tex_sub_image_2d(
2572
+ double target,
2573
+ double level,
2574
+ double xoffset,
2575
+ double yoffset,
2576
+ double width,
2577
+ double height,
2578
+ double format,
2579
+ double type,
2580
+ std::span<const double> values
2581
+ ) {
2582
+ geaTrapCheck("gea_three_webgl_tex_sub_image_2d");
2583
+ GEA_WEBGL_VOID("texSubImage2D");
2584
+ if (nativeWebGLTrapEnabled()) {
2585
+ static int logged = 0;
2586
+ if (logged++ < 12) {
2587
+ smokeLog("[gea-webgl-trap] texSubImage2D(target=0x%04x level=%d off=%d,%d %dx%d fmt=0x%04x type=0x%04x n=%zu)",
2588
+ asGLenum(target), asGLint(level), asGLint(xoffset), asGLint(yoffset),
2589
+ asGLsizei(width), asGLsizei(height), asGLenum(format), asGLenum(type), values.size());
2590
+ if (values.size() >= 3000) {
2591
+ unsigned long sum = 0;
2592
+ char preview[128];
2593
+ int pl = 0;
2594
+ for (size_t i = 2048; i < 2048 + 16 && i < values.size(); i++) {
2595
+ pl += snprintf(preview + pl, sizeof(preview) - pl, "%d,", (int)values[i]);
2596
+ }
2597
+ for (size_t i = 0; i < values.size(); i++) sum += (unsigned long)(unsigned char)(int)values[i];
2598
+ smokeLog("[gea-webgl-trap] texSubImage2D bytes@2048: %s sum%%1e9+7=%lu", preview, sum % 1000000007ul);
2599
+ }
2600
+ }
2601
+ }
2602
+ withVectorBytes(values, vectorTypeCodeForGLType(type), [&](const void *bytes, GLsizeiptr) {
2603
+ gWebGL.glTexSubImage2D(
2604
+ asGLenum(target),
2605
+ asGLint(level),
2606
+ asGLint(xoffset),
2607
+ asGLint(yoffset),
2608
+ asGLsizei(width),
2609
+ asGLsizei(height),
2610
+ asGLenum(format),
2611
+ asGLenum(type),
2612
+ bytes
2613
+ );
2614
+ });
2615
+ }
2616
+
2617
+ extern "C" double gea_three_webgl_call_f32(
2618
+ double opValue,
2619
+ double a,
2620
+ double b,
2621
+ double c,
2622
+ double d,
2623
+ double e,
2624
+ double f,
2625
+ double h,
2626
+ double i,
2627
+ std::span<const double> values
2628
+ ) {
2629
+ geaTrapCheck("gea_three_webgl_call_f32");
2630
+ if (!gWebGL.ready || !makeCurrent("eglMakeCurrent(call-f32)")) return 0.0;
2631
+ const int op = static_cast<int>(opValue);
2632
+ if (op == 18) {
2633
+ withVectorBytes(values, static_cast<int>(c), [&](const void *bytes, GLsizeiptr size) {
2634
+ nativeBufferData(static_cast<GLenum>(a), size, bytes, static_cast<GLenum>(b));
2635
+ });
2636
+ return 0.0;
2637
+ }
2638
+ if (op == 19) {
2639
+ withVectorBytes(values, static_cast<int>(c), [&](const void *bytes, GLsizeiptr size) {
2640
+ nativeBufferSubData(static_cast<GLenum>(a), static_cast<GLintptr>(b), size, bytes);
2641
+ });
2642
+ return 0.0;
2643
+ }
2644
+ if (op == 36) {
2645
+ const FloatUploadData data(values);
2646
+ gWebGL.glUniformMatrix4fv(static_cast<GLint>(a), static_cast<GLsizei>(std::max(1.0, b)), c != 0.0, data.data());
2647
+ return 0.0;
2648
+ }
2649
+ if (op == 80) {
2650
+ const FloatUploadData data(values);
2651
+ gWebGL.glUniformMatrix3fv(static_cast<GLint>(a), static_cast<GLsizei>(std::max(1.0, b)), c != 0.0, data.data());
2652
+ return 0.0;
2653
+ }
2654
+ if (op == 50) {
2655
+ withVectorBytes(values, static_cast<int>(h), [&](const void *bytes, GLsizeiptr) {
2656
+ gWebGL.glTexImage2D(
2657
+ static_cast<GLenum>(a),
2658
+ static_cast<GLint>(b),
2659
+ static_cast<GLint>(c),
2660
+ static_cast<GLsizei>(d),
2661
+ static_cast<GLsizei>(e),
2662
+ 0,
2663
+ static_cast<GLenum>(f),
2664
+ static_cast<GLenum>(h),
2665
+ bytes
2666
+ );
2667
+ });
2668
+ return 0.0;
2669
+ }
2670
+ if (op == 51) {
2671
+ withVectorBytes(values, static_cast<int>(h), [&](const void *bytes, GLsizeiptr) {
2672
+ gWebGL.glTexSubImage2D(
2673
+ static_cast<GLenum>(a),
2674
+ static_cast<GLint>(b),
2675
+ static_cast<GLint>(c),
2676
+ static_cast<GLint>(d),
2677
+ static_cast<GLsizei>(e),
2678
+ static_cast<GLsizei>(f),
2679
+ static_cast<GLenum>(h),
2680
+ static_cast<GLenum>(i),
2681
+ bytes
2682
+ );
2683
+ });
2684
+ return 0.0;
2685
+ }
2686
+ return 0.0;
2687
+ }