@geastack/native-webgl-angle 0.1.12 → 0.1.14

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.
@@ -1,5 +1,16 @@
1
1
  #include "webgl_readback.h"
2
2
  #if defined(_WIN32)
3
+ // The EGL and OpenGL ES libraries the Windows host loads beside the executable.
4
+ #ifndef GEA_WEBGL_EGL_LIBRARY
5
+ #define GEA_WEBGL_EGL_LIBRARY "libEGL.dll"
6
+ #endif
7
+ #ifndef GEA_WEBGL_GLES_LIBRARY
8
+ #define GEA_WEBGL_GLES_LIBRARY "libGLESv2.dll"
9
+ #endif
10
+ #endif
11
+ // GEA_WEBGL_STATIC_GLES: EGL/GLES are statically linked into the program, so
12
+ // nothing is loaded and entry points resolve through eglGetProcAddress.
13
+ #if defined(_WIN32)
3
14
  #ifndef NOMINMAX
4
15
  #define NOMINMAX
5
16
  #endif
@@ -7,13 +18,14 @@
7
18
  // Legacy Win16 macros are not C++ identifiers and must not leak into the runtime.
8
19
  #undef near
9
20
  #undef far
10
- #else
21
+ #elif defined(__APPLE__)
11
22
  #include "gea/apple/native_bridge.h"
12
23
 
13
24
  #import <AppKit/AppKit.h>
14
25
  #import <QuartzCore/QuartzCore.h>
15
26
  #import <dispatch/dispatch.h>
16
-
27
+ #endif
28
+ #if !defined(_WIN32) && !defined(GEA_WEBGL_STATIC_GLES)
17
29
  #include <dlfcn.h>
18
30
  #endif
19
31
  // Typed uniform arguments use the compiler runtime on both native platforms.
@@ -172,7 +184,7 @@ extern "C" bool gea_three_key_pressed(double code) {
172
184
  return gGeaKeyState[c];
173
185
  }
174
186
 
175
- #if defined(_WIN32)
187
+ #if !defined(__APPLE__)
176
188
  // The Win32 window procedure owns input on Windows; it reports into the same
177
189
  // tables the AppKit host view fills on macOS (keys as macOS virtual key codes).
178
190
  extern "C" void gea_three_set_key_state(int code, bool down) {
@@ -189,7 +201,7 @@ extern "C" void gea_three_set_pointer_state(double x, double y, double width, do
189
201
  }
190
202
  #endif
191
203
 
192
- #if !defined(_WIN32)
204
+ #if defined(__APPLE__)
193
205
  @interface GeaAngleWebGLHostView : NSView
194
206
  @end
195
207
 
@@ -410,6 +422,10 @@ using PFNGLTEXIMAGE2DPROC = void (*)(GLenum, GLint, GLint, GLsizei, GLsizei, GLi
410
422
  using PFNGLTEXPARAMETERFPROC = void (*)(GLenum, GLenum, GLfloat);
411
423
  using PFNGLTEXPARAMETERIPROC = void (*)(GLenum, GLenum, GLint);
412
424
  using PFNGLTEXSUBIMAGE2DPROC = void (*)(GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const void *);
425
+ using PFNGLTEXIMAGE3DPROC = void (*)(GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const void *);
426
+ using PFNGLTEXSUBIMAGE3DPROC = void (*)(GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const void *);
427
+ using PFNGLTEXSTORAGE3DPROC = void (*)(GLenum, GLsizei, GLenum, GLsizei, GLsizei, GLsizei);
428
+ using PFNGLFRAMEBUFFERTEXTURELAYERPROC = void (*)(GLenum, GLenum, GLuint, GLint, GLint);
413
429
  using PFNGLTEXSTORAGE2DPROC = void (*)(GLenum, GLsizei, GLenum, GLsizei, GLsizei);
414
430
  using PFNGLUNIFORM1FPROC = void (*)(GLint, GLfloat);
415
431
  using PFNGLUNIFORM1FVPROC = void (*)(GLint, GLsizei, const GLfloat *);
@@ -447,6 +463,13 @@ using PFNGLCOPYTEXIMAGE2DPROC=void(*)(GLenum,GLint,GLenum,GLint,GLint,GLsizei,GL
447
463
  using PFNGLCOPYTEXSUBIMAGE2DPROC=void(*)(GLenum,GLint,GLint,GLint,GLint,GLint,GLsizei,GLsizei);
448
464
  using PFNGLBLITFRAMEBUFFERPROC = void (*)(GLint,GLint,GLint,GLint,GLint,GLint,GLint,GLint,GLbitfield,GLenum);
449
465
  using PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC = void (*)(GLenum,GLsizei,GLenum,GLsizei,GLsizei);
466
+ using GLuint64 = std::uint64_t;
467
+ using GLsync = struct __GLsync *;
468
+ using PFNGLMAPBUFFERRANGEPROC = void *(*)(GLenum, GLintptr, GLsizeiptr, GLbitfield);
469
+ using PFNGLUNMAPBUFFERPROC = GLboolean (*)(GLenum);
470
+ using PFNGLFENCESYNCPROC = GLsync (*)(GLenum, GLbitfield);
471
+ using PFNGLCLIENTWAITSYNCPROC = GLenum (*)(GLsync, GLbitfield, GLuint64);
472
+ using PFNGLDELETESYNCPROC = void (*)(GLsync);
450
473
  struct AngleWebGLState {
451
474
  PFNGLCOPYTEXIMAGE2DPROC glCopyTexImage2D=nullptr;
452
475
  PFNGLCOPYTEXSUBIMAGE2DPROC glCopyTexSubImage2D=nullptr;
@@ -533,6 +556,12 @@ struct AngleWebGLState {
533
556
  PFNGLPIXELSTOREIPROC glPixelStorei = nullptr;
534
557
  PFNGLPOLYGONOFFSETPROC glPolygonOffset = nullptr;
535
558
  PFNGLREADPIXELSPROC glReadPixels = nullptr;
559
+ // WebGL2 asynchronous readback: PIXEL_PACK_BUFFER reads, sync objects and read-mapping.
560
+ PFNGLMAPBUFFERRANGEPROC glMapBufferRange = nullptr;
561
+ PFNGLUNMAPBUFFERPROC glUnmapBuffer = nullptr;
562
+ PFNGLFENCESYNCPROC glFenceSync = nullptr;
563
+ PFNGLCLIENTWAITSYNCPROC glClientWaitSync = nullptr;
564
+ PFNGLDELETESYNCPROC glDeleteSync = nullptr;
536
565
  PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage = nullptr;
537
566
  PFNGLSCISSORPROC glScissor = nullptr;
538
567
  PFNGLSHADERSOURCEPROC glShaderSource = nullptr;
@@ -547,6 +576,12 @@ struct AngleWebGLState {
547
576
  PFNGLTEXPARAMETERIPROC glTexParameteri = nullptr;
548
577
  PFNGLTEXSUBIMAGE2DPROC glTexSubImage2D = nullptr;
549
578
  PFNGLTEXSTORAGE2DPROC glTexStorage2D = nullptr;
579
+ // Array/3D entry points are optional: a GLES without them still starts,
580
+ // and uploads to those targets report GL_INVALID_OPERATION in the log.
581
+ PFNGLTEXIMAGE3DPROC glTexImage3D = nullptr;
582
+ PFNGLTEXSUBIMAGE3DPROC glTexSubImage3D = nullptr;
583
+ PFNGLTEXSTORAGE3DPROC glTexStorage3D = nullptr;
584
+ PFNGLFRAMEBUFFERTEXTURELAYERPROC glFramebufferTextureLayer = nullptr;
550
585
  PFNGLUNIFORM1FPROC glUniform1f = nullptr;
551
586
  PFNGLUNIFORM1FVPROC glUniform1fv = nullptr;
552
587
  PFNGLUNIFORM1IPROC glUniform1i = nullptr;
@@ -579,7 +614,7 @@ struct AngleWebGLState {
579
614
  PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = nullptr;
580
615
  PFNGLVIEWPORTPROC glViewport = nullptr;
581
616
 
582
- #if defined(_WIN32)
617
+ #if !defined(__APPLE__)
583
618
  void *nativeWindow = nullptr;
584
619
  #else
585
620
  NSView *hostView = nil;
@@ -617,7 +652,7 @@ static bool nativeShadowColorAuditEnabled() {
617
652
  }
618
653
 
619
654
  static void smokeLog(const char *format, ...) {
620
- char message[1024] = {};
655
+ char message[16384] = {};
621
656
  va_list args;
622
657
  va_start(args, format);
623
658
  std::vsnprintf(message, sizeof(message), format, args);
@@ -631,8 +666,13 @@ static void smokeLog(const char *format, ...) {
631
666
  std::fprintf(stderr, "%s\n", message);
632
667
  return;
633
668
  }
634
- #else
669
+ #elif defined(__APPLE__)
635
670
  if (!path || path[0] == '\0') path = "/tmp/three-angle-metal-smoke.log";
671
+ #else
672
+ if (!path || path[0] == '\0') {
673
+ std::fprintf(stderr, "%s\n", message);
674
+ return;
675
+ }
636
676
  #endif
637
677
  if (FILE *file = std::fopen(path, "a")) {
638
678
  std::fprintf(file, "%s\n", message);
@@ -653,7 +693,7 @@ extern "C" void gea_three_angle_smoke_log(std::string message) {
653
693
  // handler records the position in the host view's coordinate space with the
654
694
  // same top-left-origin flip the per-view path used. Returning the event
655
695
  // unmodified keeps normal dispatch (clicks, first responder) intact.
656
- #if !defined(_WIN32)
696
+ #if defined(__APPLE__)
657
697
  static id gGeaPointerMonitor = nil;
658
698
  static void geaInstallPointerMonitor(void) {
659
699
  if (gGeaPointerMonitor) return;
@@ -696,9 +736,16 @@ static void *openFirst(const char *envName, const char *const *paths) {
696
736
 
697
737
  #endif
698
738
 
739
+ #if defined(GEA_WEBGL_STATIC_GLES)
740
+ extern "C" void *eglGetProcAddress(const char *name);
741
+ #endif
699
742
  template <typename Fn>
700
743
  static Fn loadSymbol(void *library, const char *name) {
701
- #if defined(_WIN32)
744
+ #if defined(GEA_WEBGL_STATIC_GLES)
745
+ (void)library;
746
+ if (std::strcmp(name, "eglGetProcAddress") == 0) return reinterpret_cast<Fn>(&eglGetProcAddress);
747
+ return reinterpret_cast<Fn>(eglGetProcAddress(name));
748
+ #elif defined(_WIN32)
702
749
  return reinterpret_cast<Fn>(GetProcAddress(static_cast<HMODULE>(library), name));
703
750
  #else
704
751
  return reinterpret_cast<Fn>(dlsym(library, name));
@@ -797,7 +844,7 @@ static void logEglFailure(const char *step) {
797
844
  std::fprintf(stderr, "[three-angle-metal] %s failed, EGL error 0x%04x\n", step, lastEglError());
798
845
  }
799
846
 
800
- #if !defined(_WIN32)
847
+ #if defined(__APPLE__)
801
848
  static CGFloat backingScaleForHostView(NSView *nativeView) {
802
849
  CGFloat scale = nativeView.window ? nativeView.window.backingScaleFactor : 0.0;
803
850
  if (scale <= 0.0 && nativeView.window.screen) scale = nativeView.window.screen.backingScaleFactor;
@@ -870,13 +917,13 @@ extern "C" void gea_three_webgl_update_size_uwp(double width, double height, dou
870
917
  }
871
918
  #endif
872
919
 
873
- #if defined(_WIN32)
920
+ #if !defined(__APPLE__)
874
921
  // The UWP frame driver owns its EGL context exclusively until it returns to
875
922
  // CoreWindow event dispatch. Other hosts retain per-call binding semantics.
876
923
  static thread_local bool gUwpFrameContextCurrent = false;
877
924
  #endif
878
925
  static bool makeCurrent(const char *label) {
879
- #if defined(_WIN32)
926
+ #if !defined(__APPLE__)
880
927
  if (gUwpFrameContextCurrent) return true;
881
928
  #endif
882
929
  if (!gWebGL.ready && gWebGL.context == EGL_NO_CONTEXT) return false;
@@ -907,7 +954,7 @@ static bool makeCurrent(const char *label) {
907
954
  return true;
908
955
  }
909
956
 
910
- #if defined(_WIN32)
957
+ #if !defined(__APPLE__)
911
958
  extern "C" bool gea_three_webgl_begin_frame_uwp() {
912
959
  // Always rebind after the dispatcher, where arbitrary platform callbacks
913
960
  // may have used a different context. No such callbacks run within the frame.
@@ -921,7 +968,7 @@ extern "C" void gea_three_webgl_end_frame_uwp() {
921
968
  }
922
969
  #endif
923
970
 
924
- #if !defined(_WIN32)
971
+ #if defined(__APPLE__)
925
972
  static bool rebuildWindowSurface(const char *label) {
926
973
  if (!gWebGL.eglCreateWindowSurface || !gWebGL.eglDestroySurface || !gWebGL.eglMakeCurrent ||
927
974
  !gWebGL.metalLayer || !gWebGL.config || gWebGL.context == EGL_NO_CONTEXT) {
@@ -1029,6 +1076,11 @@ static bool loadGlSymbols() {
1029
1076
  LOAD_GL(glPixelStorei, PFNGLPIXELSTOREIPROC)
1030
1077
  LOAD_GL(glPolygonOffset, PFNGLPOLYGONOFFSETPROC)
1031
1078
  LOAD_GL(glReadPixels, PFNGLREADPIXELSPROC)
1079
+ LOAD_GL(glMapBufferRange, PFNGLMAPBUFFERRANGEPROC)
1080
+ LOAD_GL(glUnmapBuffer, PFNGLUNMAPBUFFERPROC)
1081
+ LOAD_GL(glFenceSync, PFNGLFENCESYNCPROC)
1082
+ LOAD_GL(glClientWaitSync, PFNGLCLIENTWAITSYNCPROC)
1083
+ LOAD_GL(glDeleteSync, PFNGLDELETESYNCPROC)
1032
1084
  LOAD_GL(glCopyTexImage2D,PFNGLCOPYTEXIMAGE2DPROC)
1033
1085
  LOAD_GL(glCopyTexSubImage2D,PFNGLCOPYTEXSUBIMAGE2DPROC)
1034
1086
  LOAD_GL(glBlitFramebuffer, PFNGLBLITFRAMEBUFFERPROC)
@@ -1047,6 +1099,10 @@ static bool loadGlSymbols() {
1047
1099
  LOAD_GL(glTexParameteri, PFNGLTEXPARAMETERIPROC)
1048
1100
  LOAD_GL(glTexSubImage2D, PFNGLTEXSUBIMAGE2DPROC)
1049
1101
  LOAD_GL(glTexStorage2D, PFNGLTEXSTORAGE2DPROC)
1102
+ gWebGL.glTexImage3D = loadGl<PFNGLTEXIMAGE3DPROC>("glTexImage3D");
1103
+ gWebGL.glTexSubImage3D = loadGl<PFNGLTEXSUBIMAGE3DPROC>("glTexSubImage3D");
1104
+ gWebGL.glTexStorage3D = loadGl<PFNGLTEXSTORAGE3DPROC>("glTexStorage3D");
1105
+ gWebGL.glFramebufferTextureLayer = loadGl<PFNGLFRAMEBUFFERTEXTURELAYERPROC>("glFramebufferTextureLayer");
1050
1106
  LOAD_GL(glUniform1f, PFNGLUNIFORM1FPROC)
1051
1107
  LOAD_GL(glUniform1fv, PFNGLUNIFORM1FVPROC)
1052
1108
  LOAD_GL(glUniform1i, PFNGLUNIFORM1IPROC)
@@ -1082,7 +1138,7 @@ static bool loadGlSymbols() {
1082
1138
  return true;
1083
1139
  }
1084
1140
 
1085
- #if defined(_WIN32)
1141
+ #if !defined(__APPLE__)
1086
1142
  extern "C" bool gea_three_webgl_attach_uwp(
1087
1143
  void *nativeWindow,
1088
1144
  #else
@@ -1097,24 +1153,50 @@ extern "C" bool gea_three_webgl_attach(
1097
1153
  smokeLog("[three-angle-metal] WebGL host attach requested");
1098
1154
  if (gWebGL.ready) return true;
1099
1155
 
1100
- #if defined(_WIN32)
1101
- // An override (e.g. threejs-rendozer's GeaRendozerGLES.dll) first, then the
1102
- // packaged ANGLE of a UWP app, then a plain desktop load next to the exe.
1103
- auto openWin32 = [](const wchar_t *overrideEnv, const wchar_t *name) -> HMODULE {
1156
+ #if defined(GEA_WEBGL_STATIC_GLES)
1157
+ // Linked into the program: nothing to load; loadSymbol resolves through
1158
+ // eglGetProcAddress. The handles only need to be non-null.
1159
+ static int staticGles;
1160
+ gWebGL.egl = &staticGles;
1161
+ gWebGL.gles = &staticGles;
1162
+ #elif defined(_WIN32)
1163
+ // An override path from the environment first, then the packaged library of
1164
+ // a UWP app, then the file beside the executable. The file names default to
1165
+ // ANGLE's; a build that stages another EGL/GLES implementation names its
1166
+ // files through GEA_WEBGL_EGL_LIBRARY / GEA_WEBGL_GLES_LIBRARY (paths
1167
+ // relative to the executable's directory; one DLL may implement both).
1168
+ auto openWin32 = [](const wchar_t *overrideEnv, const char *name) -> HMODULE {
1104
1169
  wchar_t overridePath[MAX_PATH];
1105
1170
  const DWORD n = GetEnvironmentVariableW(overrideEnv, overridePath, MAX_PATH);
1106
1171
  if (n > 0 && n < MAX_PATH) {
1107
1172
  if (HMODULE m = LoadLibraryW(overridePath)) return m;
1173
+ smokeLog("[native-webgl-angle] %ls names a library that does not load (error %lu)", overrideEnv, GetLastError());
1108
1174
  }
1175
+ std::wstring file;
1176
+ for (const char *c = name; *c; ++c) file.push_back(*c == '/' ? L'\\' : static_cast<wchar_t>(static_cast<unsigned char>(*c)));
1109
1177
  #if defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_APP
1110
- return LoadPackagedLibrary(name, 0);
1178
+ return LoadPackagedLibrary(file.c_str(), 0);
1111
1179
  #else
1112
- return LoadLibraryW(name);
1180
+ wchar_t module[MAX_PATH] = {};
1181
+ const DWORD length = GetModuleFileNameW(nullptr, module, MAX_PATH);
1182
+ std::wstring beside(module, length < MAX_PATH ? length : 0);
1183
+ const size_t slash = beside.find_last_of(L"\\/");
1184
+ if (slash != std::wstring::npos) {
1185
+ beside.resize(slash + 1);
1186
+ beside += file;
1187
+ // Altered search path: the library's own dependencies resolve from its directory.
1188
+ if (HMODULE m = LoadLibraryExW(beside.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH)) return m;
1189
+ }
1190
+ return LoadLibraryW(file.c_str());
1113
1191
  #endif
1114
1192
  };
1115
- gWebGL.egl = openWin32(L"GEA_ANGLE_EGL_DYLIB", L"libEGL.dll");
1116
- gWebGL.gles = openWin32(L"GEA_ANGLE_GLES_DYLIB", L"libGLESv2.dll");
1117
- #else
1193
+ gWebGL.egl = openWin32(L"GEA_ANGLE_EGL_DYLIB", GEA_WEBGL_EGL_LIBRARY);
1194
+ gWebGL.gles = openWin32(L"GEA_ANGLE_GLES_DYLIB", GEA_WEBGL_GLES_LIBRARY);
1195
+ if (!gWebGL.egl || !gWebGL.gles) {
1196
+ smokeLog("[native-webgl-angle] EGL/GLES implementation not found: %s and %s beside the executable "
1197
+ "(GEA_ANGLE_EGL_DYLIB / GEA_ANGLE_GLES_DYLIB override them)", GEA_WEBGL_EGL_LIBRARY, GEA_WEBGL_GLES_LIBRARY);
1198
+ }
1199
+ #elif defined(__APPLE__)
1118
1200
  static const char *const eglPaths[] = {
1119
1201
  "/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib",
1120
1202
  "/Applications/Cursor.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib",
@@ -1132,6 +1214,8 @@ extern "C" bool gea_three_webgl_attach(
1132
1214
 
1133
1215
  gWebGL.egl = openFirst("GEA_ANGLE_EGL_DYLIB", eglPaths);
1134
1216
  gWebGL.gles = openFirst("GEA_ANGLE_GLES_DYLIB", glesPaths);
1217
+ #else
1218
+ #error "load libEGL/libGLESv2 for this platform, or build with GEA_WEBGL_STATIC_GLES"
1135
1219
  #endif
1136
1220
  if (!gWebGL.egl || !gWebGL.gles) {
1137
1221
  smokeLog("[three-angle-metal] ANGLE libEGL/libGLESv2 were not found");
@@ -1209,7 +1293,7 @@ extern "C" bool gea_three_webgl_attach(
1209
1293
  }
1210
1294
  gWebGL.config = config;
1211
1295
 
1212
- #if defined(_WIN32)
1296
+ #if !defined(__APPLE__)
1213
1297
  gWebGL.nativeWindow = nativeWindow;
1214
1298
  gea_three_webgl_update_size_uwp(width, height, devicePixelRatio);
1215
1299
  if (gWebGL.eglCreateWindowSurface) {
@@ -1275,7 +1359,7 @@ extern "C" bool gea_three_webgl_attach(
1275
1359
 
1276
1360
  gWebGL.ready = true;
1277
1361
  gWebGL.glViewport(0, 0, gWebGL.widthPx, gWebGL.heightPx);
1278
- #if !defined(_WIN32)
1362
+ #if defined(__APPLE__)
1279
1363
  dispatch_async(dispatch_get_main_queue(), ^{
1280
1364
  if (gWebGL.ready && rebuildWindowSurface("eglCreateWindowSurface(deferred)")) {
1281
1365
  smokeLog("[three-angle-metal] WebGL EGL surface rebound at %dx%d", gWebGL.widthPx, gWebGL.heightPx);
@@ -1391,7 +1475,7 @@ extern "C" void gea_three_webgl_swap() {
1391
1475
  // TEMP-DIAG: GEA_THREE_VIEW_DUMP=1 — at frame 60, log the window's full
1392
1476
  // subview tree and a hit-test grid so overlay views that would swallow
1393
1477
  // mouse events are visible without Accessibility permission.
1394
- #if !defined(_WIN32)
1478
+ #if defined(__APPLE__)
1395
1479
  if (std::getenv("GEA_THREE_VIEW_DUMP")) {
1396
1480
  static int viewDumpCount = 0;
1397
1481
  if (++viewDumpCount == 60) {
@@ -1424,6 +1508,16 @@ extern "C" void gea_three_webgl_swap() {
1424
1508
  }
1425
1509
  }
1426
1510
  #endif
1511
+ // The debug dump and recording read the window into client memory. A PIXEL_PACK_BUFFER the app left
1512
+ // bound (WebGL2 asynchronous readback) would make the pointer a buffer offset instead, so the read
1513
+ // runs with it unbound and the binding is restored afterwards.
1514
+ const auto readWindowRgba = [](int w, int h, unsigned char *rgba) {
1515
+ GLint packBuffer = 0;
1516
+ if (gWebGL.glGetIntegerv) gWebGL.glGetIntegerv(0x88ED /* PIXEL_PACK_BUFFER_BINDING */, &packBuffer);
1517
+ if (packBuffer) gWebGL.glBindBuffer(0x88EB /* PIXEL_PACK_BUFFER */, 0);
1518
+ gWebGL.glReadPixels(0, 0, w, h, GL_RGBA_VALUE, GL_UNSIGNED_BYTE, rgba);
1519
+ if (packBuffer) gWebGL.glBindBuffer(0x88EB, static_cast<GLuint>(packBuffer));
1520
+ };
1427
1521
  // Debug frame dump: GEA_WEBGL_DUMP_FRAME=<path-prefix> writes the full
1428
1522
  // framebuffer of frames 90/180/300 as PPM (P6) — visual ground truth for
1429
1523
  // native-vs-browser comparisons without needing a window screenshot.
@@ -1434,7 +1528,7 @@ extern "C" void gea_three_webgl_swap() {
1434
1528
  const int w = gWebGL.widthPx;
1435
1529
  const int h = gWebGL.heightPx;
1436
1530
  std::vector<unsigned char> rgba(static_cast<size_t>(w) * h * 4);
1437
- gWebGL.glReadPixels(0, 0, w, h, GL_RGBA_VALUE, GL_UNSIGNED_BYTE, rgba.data());
1531
+ readWindowRgba(w, h, rgba.data());
1438
1532
  char path[512];
1439
1533
  std::snprintf(path, sizeof path, "%s-f%d.ppm", dumpPrefix, dumpCount);
1440
1534
  if (FILE *file = std::fopen(path, "wb")) {
@@ -1483,7 +1577,7 @@ extern "C" void gea_three_webgl_swap() {
1483
1577
  if (due > recordWritten && gWebGL.widthPx == recordW && gWebGL.heightPx == recordH) {
1484
1578
  std::vector<unsigned char> rgba(static_cast<size_t>(recordW) * recordH * 4);
1485
1579
  std::vector<unsigned char> rgb(static_cast<size_t>(recordW) * recordH * 3);
1486
- gWebGL.glReadPixels(0, 0, recordW, recordH, GL_RGBA_VALUE, GL_UNSIGNED_BYTE, rgba.data());
1580
+ readWindowRgba(recordW, recordH, rgba.data());
1487
1581
  for (int y = 0; y < recordH; ++y) {
1488
1582
  const unsigned char *src = rgba.data() + static_cast<size_t>(recordH - 1 - y) * recordW * 4;
1489
1583
  unsigned char *dst = rgb.data() + static_cast<size_t>(y) * recordW * 3;
@@ -2498,6 +2592,36 @@ extern "C" void gea_three_webgl_tex_image_2d_bytes(double target, double level,
2498
2592
  gWebGL.glTexImage2D(asGLenum(target), asGLint(level), asGLint(internalFormat), asGLsizei(width), asGLsizei(height), 0, asGLenum(format), asGLenum(type), bytes);
2499
2593
  }
2500
2594
 
2595
+ static bool requireGl(const void *fn, const char *name) {
2596
+ if (fn) return true;
2597
+ smokeLog("[native-webgl] %s is not provided by this GLES; the call is dropped", name);
2598
+ return false;
2599
+ }
2600
+
2601
+ extern "C" void gea_three_webgl_tex_image_3d_bytes(double target, double level, double internalFormat, double width, double height, double depth, double format, double type, const void* bytes, double) {
2602
+ GEA_WEBGL_VOID("texImage3DBytes");
2603
+ if (!requireGl(reinterpret_cast<const void *>(gWebGL.glTexImage3D), "glTexImage3D")) return;
2604
+ gWebGL.glTexImage3D(asGLenum(target), asGLint(level), asGLint(internalFormat), asGLsizei(width), asGLsizei(height), asGLsizei(depth), 0, asGLenum(format), asGLenum(type), bytes);
2605
+ }
2606
+
2607
+ extern "C" void gea_three_webgl_tex_sub_image_3d_bytes(double target, double level, double xoffset, double yoffset, double zoffset, double width, double height, double depth, double format, double type, const void* bytes, double) {
2608
+ GEA_WEBGL_VOID("texSubImage3DBytes");
2609
+ if (!requireGl(reinterpret_cast<const void *>(gWebGL.glTexSubImage3D), "glTexSubImage3D")) return;
2610
+ gWebGL.glTexSubImage3D(asGLenum(target), asGLint(level), asGLint(xoffset), asGLint(yoffset), asGLint(zoffset), asGLsizei(width), asGLsizei(height), asGLsizei(depth), asGLenum(format), asGLenum(type), bytes);
2611
+ }
2612
+
2613
+ extern "C" void gea_three_webgl_tex_storage_3d(double target, double levels, double internalFormat, double width, double height, double depth) {
2614
+ GEA_WEBGL_VOID("texStorage3D");
2615
+ if (!requireGl(reinterpret_cast<const void *>(gWebGL.glTexStorage3D), "glTexStorage3D")) return;
2616
+ gWebGL.glTexStorage3D(asGLenum(target), asGLsizei(std::max(1.0, levels)), asGLenum(internalFormat), asGLsizei(width), asGLsizei(height), asGLsizei(depth));
2617
+ }
2618
+
2619
+ extern "C" void gea_three_webgl_framebuffer_texture_layer(double target, double attachment, double texture, double level, double layer) {
2620
+ GEA_WEBGL_VOID("framebufferTextureLayer");
2621
+ if (!requireGl(reinterpret_cast<const void *>(gWebGL.glFramebufferTextureLayer), "glFramebufferTextureLayer")) return;
2622
+ gWebGL.glFramebufferTextureLayer(asGLenum(target), asGLenum(attachment), asGLuint(texture), asGLint(level), asGLint(layer));
2623
+ }
2624
+
2501
2625
  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) {
2502
2626
  GEA_WEBGL_VOID("texSubImage2DBytes");
2503
2627
  gWebGL.glTexSubImage2D(asGLenum(target), asGLint(level), asGLint(xoffset), asGLint(yoffset), asGLsizei(width), asGLsizei(height), asGLenum(format), asGLenum(type), bytes);
@@ -2805,6 +2929,106 @@ extern "C" void gea_three_webgl_read_pixels_bytes(double x, double y, double wid
2805
2929
  gWebGL.glReadPixels(asGLint(x),asGLint(y),asGLsizei(width),asGLsizei(height),asGLenum(format),asGLenum(type),bytes);
2806
2930
  }
2807
2931
 
2932
+ // WebGL2 asynchronous readback (three's readRenderTargetPixelsAsync): readPixels into the bound
2933
+ // PIXEL_PACK_BUFFER at a byte offset, a fence, polling, then getBufferSubData through a read mapping.
2934
+ extern "C" void gea_three_webgl_read_pixels_pack(double x, double y, double width, double height, double format, double type, double offset) {
2935
+ GEA_WEBGL_VOID("readPixels");
2936
+ GLint packBuffer = 0;
2937
+ gWebGL.glGetIntegerv(0x88ED, &packBuffer);
2938
+ if (!packBuffer || !std::isfinite(offset) || offset < 0 || std::floor(offset) != offset) { if (!hostReadbackError) hostReadbackError = 0x0502; return; }
2939
+ gWebGL.glReadPixels(asGLint(x), asGLint(y), asGLsizei(width), asGLsizei(height), asGLenum(format), asGLenum(type),
2940
+ reinterpret_cast<void *>(static_cast<std::uintptr_t>(offset)));
2941
+ }
2942
+ static std::unordered_map<std::uint32_t, GLsync> gWebGLSyncs;
2943
+ static std::uint32_t gWebGLNextSync = 1;
2944
+ extern "C" double gea_three_webgl_fence_sync() {
2945
+ GEA_WEBGL_DOUBLE("fenceSync");
2946
+ GLsync sync = gWebGL.glFenceSync(0x9117 /* SYNC_GPU_COMMANDS_COMPLETE */, 0);
2947
+ if (!sync) return 0.0;
2948
+ const std::uint32_t id = gWebGLNextSync++;
2949
+ gWebGLSyncs[id] = sync;
2950
+ return static_cast<double>(id);
2951
+ }
2952
+ extern "C" double gea_three_webgl_client_wait_sync(double sync, double flags, double timeout) {
2953
+ GEA_WEBGL_DOUBLE("clientWaitSync");
2954
+ auto it = gWebGLSyncs.find(static_cast<std::uint32_t>(sync));
2955
+ if (it == gWebGLSyncs.end()) return 0x911D; // WAIT_FAILED
2956
+ return static_cast<double>(gWebGL.glClientWaitSync(it->second, static_cast<GLbitfield>(flags), static_cast<GLuint64>(std::max(0.0, timeout))));
2957
+ }
2958
+ extern "C" void gea_three_webgl_delete_sync(double sync) {
2959
+ GEA_WEBGL_VOID("deleteSync");
2960
+ auto it = gWebGLSyncs.find(static_cast<std::uint32_t>(sync));
2961
+ if (it == gWebGLSyncs.end()) return;
2962
+ gWebGL.glDeleteSync(it->second);
2963
+ gWebGLSyncs.erase(it);
2964
+ }
2965
+ // GPU timers for the app's frame profiler, on GL_EXT_disjoint_timer_query.
2966
+ struct HostGpuTimers {
2967
+ bool initialized = false, supported = false;
2968
+ void (*gen)(GLsizei, GLuint *) = nullptr;
2969
+ void (*remove)(GLsizei, const GLuint *) = nullptr;
2970
+ void (*begin)(GLenum, GLuint) = nullptr;
2971
+ void (*end)(GLenum) = nullptr;
2972
+ void (*available)(GLuint, GLenum, GLuint *) = nullptr;
2973
+ void (*result)(GLuint, GLenum, std::uint64_t *) = nullptr;
2974
+ bool open = false;
2975
+ bool ensure() {
2976
+ if (initialized) return supported;
2977
+ initialized = true;
2978
+ const auto extensions = reinterpret_cast<const char *>(gWebGL.glGetString(0x1F03));
2979
+ const std::string list = " " + std::string(extensions ? extensions : "") + " ";
2980
+ if (list.find(" GL_EXT_disjoint_timer_query ") == std::string::npos) return false;
2981
+ gen = loadGl<decltype(gen)>("glGenQueriesEXT");
2982
+ remove = loadGl<decltype(remove)>("glDeleteQueriesEXT");
2983
+ begin = loadGl<decltype(begin)>("glBeginQueryEXT");
2984
+ end = loadGl<decltype(end)>("glEndQueryEXT");
2985
+ available = loadGl<decltype(available)>("glGetQueryObjectuivEXT");
2986
+ result = loadGl<decltype(result)>("glGetQueryObjectui64vEXT");
2987
+ supported = gen && remove && begin && end && available && result;
2988
+ return supported;
2989
+ }
2990
+ };
2991
+ static HostGpuTimers gHostGpuTimers;
2992
+ extern "C" double gea_three_webgl_timer_begin() {
2993
+ GEA_WEBGL_DOUBLE("timerBegin");
2994
+ auto &t = gHostGpuTimers;
2995
+ if (!t.ensure() || t.open) return 0.0;
2996
+ GLuint query = 0;
2997
+ t.gen(1, &query);
2998
+ if (!query) return 0.0;
2999
+ t.begin(0x88BF /* TIME_ELAPSED_EXT */, query);
3000
+ t.open = true;
3001
+ return static_cast<double>(query);
3002
+ }
3003
+ extern "C" void gea_three_webgl_timer_end() {
3004
+ GEA_WEBGL_VOID("timerEnd");
3005
+ auto &t = gHostGpuTimers;
3006
+ if (!t.supported || !t.open) return;
3007
+ t.end(0x88BF);
3008
+ t.open = false;
3009
+ }
3010
+ extern "C" double gea_three_webgl_timer_result(double timer) {
3011
+ GEA_WEBGL_DOUBLE("timerResult");
3012
+ auto &t = gHostGpuTimers;
3013
+ const GLuint query = static_cast<GLuint>(timer);
3014
+ if (!t.supported || !query) return 0.0;
3015
+ GLuint ready = 0;
3016
+ t.available(query, 0x8867 /* QUERY_RESULT_AVAILABLE */, &ready);
3017
+ if (!ready) return -1.0;
3018
+ std::uint64_t ns = 0;
3019
+ t.result(query, 0x8866 /* QUERY_RESULT */, &ns);
3020
+ t.remove(1, &query);
3021
+ return static_cast<double>(ns);
3022
+ }
3023
+ extern "C" void gea_three_webgl_get_buffer_sub_data_bytes(double target, double srcOffset, void *bytes, double byteLength) {
3024
+ GEA_WEBGL_VOID("getBufferSubData");
3025
+ if (!bytes || !(byteLength > 0) || !(srcOffset >= 0)) { if (!hostReadbackError) hostReadbackError = 0x0501; return; }
3026
+ void *mapped = gWebGL.glMapBufferRange(asGLenum(target), static_cast<GLintptr>(srcOffset), static_cast<GLsizeiptr>(byteLength), 0x0001 /* MAP_READ_BIT */);
3027
+ if (!mapped) { if (!hostReadbackError) hostReadbackError = 0x0502; return; }
3028
+ std::memcpy(bytes, mapped, static_cast<std::size_t>(byteLength));
3029
+ gWebGL.glUnmapBuffer(asGLenum(target));
3030
+ }
3031
+
2808
3032
  extern "C" void gea_three_webgl_blit_framebuffer(double sx0,double sy0,double sx1,double sy1,double dx0,double dy0,double dx1,double dy1,double mask,double filter) {
2809
3033
  GEA_WEBGL_VOID("blitFramebuffer");
2810
3034
  gWebGL.glBlitFramebuffer(asGLint(sx0),asGLint(sy0),asGLint(sx1),asGLint(sy1),asGLint(dx0),asGLint(dy0),asGLint(dx1),asGLint(dy1),asGLenum(mask),asGLenum(filter));
@@ -21,6 +21,9 @@
21
21
  #if defined(_WIN32)
22
22
  #define _USE_MATH_DEFINES
23
23
  #include "audio_uwp.h"
24
+ // Legacy Win16 macros are not C++ identifiers and must not leak into the runtime.
25
+ #undef near
26
+ #undef far
24
27
  #else
25
28
  #import <AVFoundation/AVFoundation.h>
26
29
  #import <Foundation/Foundation.h>
@@ -44,8 +47,30 @@
44
47
 
45
48
  #if defined(_WIN32)
46
49
  static std::wstring geaAudioAssetDirectory;
50
+ static bool geaAudioAssetDirectorySet = false;
47
51
  extern "C" void gea_three_audio_set_directory_uwp(const wchar_t *path) {
48
52
  geaAudioAssetDirectory = path ? path : L"";
53
+ geaAudioAssetDirectorySet = true;
54
+ }
55
+ // Without a driver naming the directory: GEA_THREE_AUDIO_DIR, else the
56
+ // Resources\Sounds folder beside the executable (where the desktop target
57
+ // stages an app's sounds), matching the macOS host's bundle lookup.
58
+ static const std::wstring &geaAudioDirectory() {
59
+ if (!geaAudioAssetDirectorySet) {
60
+ geaAudioAssetDirectorySet = true;
61
+ wchar_t value[MAX_PATH] = {};
62
+ const DWORD n = GetEnvironmentVariableW(L"GEA_THREE_AUDIO_DIR", value, MAX_PATH);
63
+ if (n > 0 && n < MAX_PATH) {
64
+ geaAudioAssetDirectory = value;
65
+ } else {
66
+ wchar_t module[MAX_PATH] = {};
67
+ const DWORD length = GetModuleFileNameW(nullptr, module, MAX_PATH);
68
+ std::wstring directory(module, length < MAX_PATH ? length : 0);
69
+ const size_t slash = directory.find_last_of(L"\\/");
70
+ if (slash != std::wstring::npos) geaAudioAssetDirectory = directory.substr(0, slash) + L"\\Resources\\Sounds";
71
+ }
72
+ }
73
+ return geaAudioAssetDirectory;
49
74
  }
50
75
  #endif
51
76
 
@@ -427,7 +452,7 @@ struct GeaAudioEngine {
427
452
  long loadBufferFile(const std::string &name) {
428
453
  if (name.empty() || name.find('/') != std::string::npos || name.find("..") != std::string::npos) return 0;
429
454
  #if defined(_WIN32)
430
- if (name.find('\\') != std::string::npos || geaAudioAssetDirectory.empty()) return 0;
455
+ if (name.find('\\') != std::string::npos || geaAudioDirectory().empty()) return 0;
431
456
  const int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, name.data(), static_cast<int>(name.size()), nullptr, 0);
432
457
  if (length <= 0) return 0;
433
458
  std::wstring base(static_cast<size_t>(length), L'\0');