@geastack/native-webgl-angle 0.1.11 → 0.1.13

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,15 +1,22 @@
1
+ #include "webgl_readback.h"
2
+ // GEA_WEBGL_STATIC_GLES: EGL/GLES are statically linked into the program, so
3
+ // nothing is loaded and entry points resolve through eglGetProcAddress.
1
4
  #if defined(_WIN32)
2
5
  #ifndef NOMINMAX
3
6
  #define NOMINMAX
4
7
  #endif
5
8
  #include <windows.h>
6
- #else
9
+ // Legacy Win16 macros are not C++ identifiers and must not leak into the runtime.
10
+ #undef near
11
+ #undef far
12
+ #elif defined(__APPLE__)
7
13
  #include "gea/apple/native_bridge.h"
8
14
 
9
15
  #import <AppKit/AppKit.h>
10
16
  #import <QuartzCore/QuartzCore.h>
11
17
  #import <dispatch/dispatch.h>
12
-
18
+ #endif
19
+ #if !defined(_WIN32) && !defined(GEA_WEBGL_STATIC_GLES)
13
20
  #include <dlfcn.h>
14
21
  #endif
15
22
  // Typed uniform arguments use the compiler runtime on both native platforms.
@@ -168,7 +175,7 @@ extern "C" bool gea_three_key_pressed(double code) {
168
175
  return gGeaKeyState[c];
169
176
  }
170
177
 
171
- #if defined(_WIN32)
178
+ #if !defined(__APPLE__)
172
179
  // The Win32 window procedure owns input on Windows; it reports into the same
173
180
  // tables the AppKit host view fills on macOS (keys as macOS virtual key codes).
174
181
  extern "C" void gea_three_set_key_state(int code, bool down) {
@@ -185,7 +192,7 @@ extern "C" void gea_three_set_pointer_state(double x, double y, double width, do
185
192
  }
186
193
  #endif
187
194
 
188
- #if !defined(_WIN32)
195
+ #if defined(__APPLE__)
189
196
  @interface GeaAngleWebGLHostView : NSView
190
197
  @end
191
198
 
@@ -406,6 +413,10 @@ using PFNGLTEXIMAGE2DPROC = void (*)(GLenum, GLint, GLint, GLsizei, GLsizei, GLi
406
413
  using PFNGLTEXPARAMETERFPROC = void (*)(GLenum, GLenum, GLfloat);
407
414
  using PFNGLTEXPARAMETERIPROC = void (*)(GLenum, GLenum, GLint);
408
415
  using PFNGLTEXSUBIMAGE2DPROC = void (*)(GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const void *);
416
+ using PFNGLTEXIMAGE3DPROC = void (*)(GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const void *);
417
+ using PFNGLTEXSUBIMAGE3DPROC = void (*)(GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const void *);
418
+ using PFNGLTEXSTORAGE3DPROC = void (*)(GLenum, GLsizei, GLenum, GLsizei, GLsizei, GLsizei);
419
+ using PFNGLFRAMEBUFFERTEXTURELAYERPROC = void (*)(GLenum, GLenum, GLuint, GLint, GLint);
409
420
  using PFNGLTEXSTORAGE2DPROC = void (*)(GLenum, GLsizei, GLenum, GLsizei, GLsizei);
410
421
  using PFNGLUNIFORM1FPROC = void (*)(GLint, GLfloat);
411
422
  using PFNGLUNIFORM1FVPROC = void (*)(GLint, GLsizei, const GLfloat *);
@@ -439,7 +450,22 @@ using PFNGLVERTEXATTRIBDIVISORPROC = void (*)(GLuint, GLuint);
439
450
  using PFNGLVERTEXATTRIBPOINTERPROC = void (*)(GLuint, GLint, GLenum, GLboolean, GLsizei, const void *);
440
451
  using PFNGLVIEWPORTPROC = void (*)(GLint, GLint, GLsizei, GLsizei);
441
452
 
453
+ using PFNGLCOPYTEXIMAGE2DPROC=void(*)(GLenum,GLint,GLenum,GLint,GLint,GLsizei,GLsizei,GLint);
454
+ using PFNGLCOPYTEXSUBIMAGE2DPROC=void(*)(GLenum,GLint,GLint,GLint,GLint,GLint,GLsizei,GLsizei);
455
+ using PFNGLBLITFRAMEBUFFERPROC = void (*)(GLint,GLint,GLint,GLint,GLint,GLint,GLint,GLint,GLbitfield,GLenum);
456
+ using PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC = void (*)(GLenum,GLsizei,GLenum,GLsizei,GLsizei);
457
+ using GLuint64 = std::uint64_t;
458
+ using GLsync = struct __GLsync *;
459
+ using PFNGLMAPBUFFERRANGEPROC = void *(*)(GLenum, GLintptr, GLsizeiptr, GLbitfield);
460
+ using PFNGLUNMAPBUFFERPROC = GLboolean (*)(GLenum);
461
+ using PFNGLFENCESYNCPROC = GLsync (*)(GLenum, GLbitfield);
462
+ using PFNGLCLIENTWAITSYNCPROC = GLenum (*)(GLsync, GLbitfield, GLuint64);
463
+ using PFNGLDELETESYNCPROC = void (*)(GLsync);
442
464
  struct AngleWebGLState {
465
+ PFNGLCOPYTEXIMAGE2DPROC glCopyTexImage2D=nullptr;
466
+ PFNGLCOPYTEXSUBIMAGE2DPROC glCopyTexSubImage2D=nullptr;
467
+ PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer = nullptr;
468
+ PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glRenderbufferStorageMultisample = nullptr;
443
469
  void *egl = nullptr;
444
470
  void *gles = nullptr;
445
471
  EGLDisplay display = EGL_NO_DISPLAY;
@@ -521,6 +547,12 @@ struct AngleWebGLState {
521
547
  PFNGLPIXELSTOREIPROC glPixelStorei = nullptr;
522
548
  PFNGLPOLYGONOFFSETPROC glPolygonOffset = nullptr;
523
549
  PFNGLREADPIXELSPROC glReadPixels = nullptr;
550
+ // WebGL2 asynchronous readback: PIXEL_PACK_BUFFER reads, sync objects and read-mapping.
551
+ PFNGLMAPBUFFERRANGEPROC glMapBufferRange = nullptr;
552
+ PFNGLUNMAPBUFFERPROC glUnmapBuffer = nullptr;
553
+ PFNGLFENCESYNCPROC glFenceSync = nullptr;
554
+ PFNGLCLIENTWAITSYNCPROC glClientWaitSync = nullptr;
555
+ PFNGLDELETESYNCPROC glDeleteSync = nullptr;
524
556
  PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage = nullptr;
525
557
  PFNGLSCISSORPROC glScissor = nullptr;
526
558
  PFNGLSHADERSOURCEPROC glShaderSource = nullptr;
@@ -535,6 +567,12 @@ struct AngleWebGLState {
535
567
  PFNGLTEXPARAMETERIPROC glTexParameteri = nullptr;
536
568
  PFNGLTEXSUBIMAGE2DPROC glTexSubImage2D = nullptr;
537
569
  PFNGLTEXSTORAGE2DPROC glTexStorage2D = nullptr;
570
+ // Array/3D entry points are optional: a GLES without them still starts,
571
+ // and uploads to those targets report GL_INVALID_OPERATION in the log.
572
+ PFNGLTEXIMAGE3DPROC glTexImage3D = nullptr;
573
+ PFNGLTEXSUBIMAGE3DPROC glTexSubImage3D = nullptr;
574
+ PFNGLTEXSTORAGE3DPROC glTexStorage3D = nullptr;
575
+ PFNGLFRAMEBUFFERTEXTURELAYERPROC glFramebufferTextureLayer = nullptr;
538
576
  PFNGLUNIFORM1FPROC glUniform1f = nullptr;
539
577
  PFNGLUNIFORM1FVPROC glUniform1fv = nullptr;
540
578
  PFNGLUNIFORM1IPROC glUniform1i = nullptr;
@@ -567,7 +605,7 @@ struct AngleWebGLState {
567
605
  PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = nullptr;
568
606
  PFNGLVIEWPORTPROC glViewport = nullptr;
569
607
 
570
- #if defined(_WIN32)
608
+ #if !defined(__APPLE__)
571
609
  void *nativeWindow = nullptr;
572
610
  #else
573
611
  NSView *hostView = nil;
@@ -580,6 +618,7 @@ struct AngleWebGLState {
580
618
  };
581
619
 
582
620
  static AngleWebGLState gWebGL;
621
+ static GLenum hostReadbackError = 0;
583
622
  static std::chrono::steady_clock::time_point gProfileSizeSync;
584
623
  static bool gProfileSizeSyncPending = false;
585
624
  static bool nativeFrameProfileEnabled() {
@@ -604,7 +643,7 @@ static bool nativeShadowColorAuditEnabled() {
604
643
  }
605
644
 
606
645
  static void smokeLog(const char *format, ...) {
607
- char message[1024] = {};
646
+ char message[16384] = {};
608
647
  va_list args;
609
648
  va_start(args, format);
610
649
  std::vsnprintf(message, sizeof(message), format, args);
@@ -618,8 +657,13 @@ static void smokeLog(const char *format, ...) {
618
657
  std::fprintf(stderr, "%s\n", message);
619
658
  return;
620
659
  }
621
- #else
660
+ #elif defined(__APPLE__)
622
661
  if (!path || path[0] == '\0') path = "/tmp/three-angle-metal-smoke.log";
662
+ #else
663
+ if (!path || path[0] == '\0') {
664
+ std::fprintf(stderr, "%s\n", message);
665
+ return;
666
+ }
623
667
  #endif
624
668
  if (FILE *file = std::fopen(path, "a")) {
625
669
  std::fprintf(file, "%s\n", message);
@@ -640,7 +684,7 @@ extern "C" void gea_three_angle_smoke_log(std::string message) {
640
684
  // handler records the position in the host view's coordinate space with the
641
685
  // same top-left-origin flip the per-view path used. Returning the event
642
686
  // unmodified keeps normal dispatch (clicks, first responder) intact.
643
- #if !defined(_WIN32)
687
+ #if defined(__APPLE__)
644
688
  static id gGeaPointerMonitor = nil;
645
689
  static void geaInstallPointerMonitor(void) {
646
690
  if (gGeaPointerMonitor) return;
@@ -683,9 +727,16 @@ static void *openFirst(const char *envName, const char *const *paths) {
683
727
 
684
728
  #endif
685
729
 
730
+ #if defined(GEA_WEBGL_STATIC_GLES)
731
+ extern "C" void *eglGetProcAddress(const char *name);
732
+ #endif
686
733
  template <typename Fn>
687
734
  static Fn loadSymbol(void *library, const char *name) {
688
- #if defined(_WIN32)
735
+ #if defined(GEA_WEBGL_STATIC_GLES)
736
+ (void)library;
737
+ if (std::strcmp(name, "eglGetProcAddress") == 0) return reinterpret_cast<Fn>(&eglGetProcAddress);
738
+ return reinterpret_cast<Fn>(eglGetProcAddress(name));
739
+ #elif defined(_WIN32)
689
740
  return reinterpret_cast<Fn>(GetProcAddress(static_cast<HMODULE>(library), name));
690
741
  #else
691
742
  return reinterpret_cast<Fn>(dlsym(library, name));
@@ -784,7 +835,7 @@ static void logEglFailure(const char *step) {
784
835
  std::fprintf(stderr, "[three-angle-metal] %s failed, EGL error 0x%04x\n", step, lastEglError());
785
836
  }
786
837
 
787
- #if !defined(_WIN32)
838
+ #if defined(__APPLE__)
788
839
  static CGFloat backingScaleForHostView(NSView *nativeView) {
789
840
  CGFloat scale = nativeView.window ? nativeView.window.backingScaleFactor : 0.0;
790
841
  if (scale <= 0.0 && nativeView.window.screen) scale = nativeView.window.screen.backingScaleFactor;
@@ -857,13 +908,13 @@ extern "C" void gea_three_webgl_update_size_uwp(double width, double height, dou
857
908
  }
858
909
  #endif
859
910
 
860
- #if defined(_WIN32)
911
+ #if !defined(__APPLE__)
861
912
  // The UWP frame driver owns its EGL context exclusively until it returns to
862
913
  // CoreWindow event dispatch. Other hosts retain per-call binding semantics.
863
914
  static thread_local bool gUwpFrameContextCurrent = false;
864
915
  #endif
865
916
  static bool makeCurrent(const char *label) {
866
- #if defined(_WIN32)
917
+ #if !defined(__APPLE__)
867
918
  if (gUwpFrameContextCurrent) return true;
868
919
  #endif
869
920
  if (!gWebGL.ready && gWebGL.context == EGL_NO_CONTEXT) return false;
@@ -894,7 +945,7 @@ static bool makeCurrent(const char *label) {
894
945
  return true;
895
946
  }
896
947
 
897
- #if defined(_WIN32)
948
+ #if !defined(__APPLE__)
898
949
  extern "C" bool gea_three_webgl_begin_frame_uwp() {
899
950
  // Always rebind after the dispatcher, where arbitrary platform callbacks
900
951
  // may have used a different context. No such callbacks run within the frame.
@@ -908,7 +959,7 @@ extern "C" void gea_three_webgl_end_frame_uwp() {
908
959
  }
909
960
  #endif
910
961
 
911
- #if !defined(_WIN32)
962
+ #if defined(__APPLE__)
912
963
  static bool rebuildWindowSurface(const char *label) {
913
964
  if (!gWebGL.eglCreateWindowSurface || !gWebGL.eglDestroySurface || !gWebGL.eglMakeCurrent ||
914
965
  !gWebGL.metalLayer || !gWebGL.config || gWebGL.context == EGL_NO_CONTEXT) {
@@ -1016,6 +1067,15 @@ static bool loadGlSymbols() {
1016
1067
  LOAD_GL(glPixelStorei, PFNGLPIXELSTOREIPROC)
1017
1068
  LOAD_GL(glPolygonOffset, PFNGLPOLYGONOFFSETPROC)
1018
1069
  LOAD_GL(glReadPixels, PFNGLREADPIXELSPROC)
1070
+ LOAD_GL(glMapBufferRange, PFNGLMAPBUFFERRANGEPROC)
1071
+ LOAD_GL(glUnmapBuffer, PFNGLUNMAPBUFFERPROC)
1072
+ LOAD_GL(glFenceSync, PFNGLFENCESYNCPROC)
1073
+ LOAD_GL(glClientWaitSync, PFNGLCLIENTWAITSYNCPROC)
1074
+ LOAD_GL(glDeleteSync, PFNGLDELETESYNCPROC)
1075
+ LOAD_GL(glCopyTexImage2D,PFNGLCOPYTEXIMAGE2DPROC)
1076
+ LOAD_GL(glCopyTexSubImage2D,PFNGLCOPYTEXSUBIMAGE2DPROC)
1077
+ LOAD_GL(glBlitFramebuffer, PFNGLBLITFRAMEBUFFERPROC)
1078
+ LOAD_GL(glRenderbufferStorageMultisample, PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)
1019
1079
  LOAD_GL(glRenderbufferStorage, PFNGLRENDERBUFFERSTORAGEPROC)
1020
1080
  LOAD_GL(glScissor, PFNGLSCISSORPROC)
1021
1081
  LOAD_GL(glShaderSource, PFNGLSHADERSOURCEPROC)
@@ -1030,6 +1090,10 @@ static bool loadGlSymbols() {
1030
1090
  LOAD_GL(glTexParameteri, PFNGLTEXPARAMETERIPROC)
1031
1091
  LOAD_GL(glTexSubImage2D, PFNGLTEXSUBIMAGE2DPROC)
1032
1092
  LOAD_GL(glTexStorage2D, PFNGLTEXSTORAGE2DPROC)
1093
+ gWebGL.glTexImage3D = loadGl<PFNGLTEXIMAGE3DPROC>("glTexImage3D");
1094
+ gWebGL.glTexSubImage3D = loadGl<PFNGLTEXSUBIMAGE3DPROC>("glTexSubImage3D");
1095
+ gWebGL.glTexStorage3D = loadGl<PFNGLTEXSTORAGE3DPROC>("glTexStorage3D");
1096
+ gWebGL.glFramebufferTextureLayer = loadGl<PFNGLFRAMEBUFFERTEXTURELAYERPROC>("glFramebufferTextureLayer");
1033
1097
  LOAD_GL(glUniform1f, PFNGLUNIFORM1FPROC)
1034
1098
  LOAD_GL(glUniform1fv, PFNGLUNIFORM1FVPROC)
1035
1099
  LOAD_GL(glUniform1i, PFNGLUNIFORM1IPROC)
@@ -1065,7 +1129,7 @@ static bool loadGlSymbols() {
1065
1129
  return true;
1066
1130
  }
1067
1131
 
1068
- #if defined(_WIN32)
1132
+ #if !defined(__APPLE__)
1069
1133
  extern "C" bool gea_three_webgl_attach_uwp(
1070
1134
  void *nativeWindow,
1071
1135
  #else
@@ -1080,7 +1144,13 @@ extern "C" bool gea_three_webgl_attach(
1080
1144
  smokeLog("[three-angle-metal] WebGL host attach requested");
1081
1145
  if (gWebGL.ready) return true;
1082
1146
 
1083
- #if defined(_WIN32)
1147
+ #if defined(GEA_WEBGL_STATIC_GLES)
1148
+ // Linked into the program: nothing to load; loadSymbol resolves through
1149
+ // eglGetProcAddress. The handles only need to be non-null.
1150
+ static int staticGles;
1151
+ gWebGL.egl = &staticGles;
1152
+ gWebGL.gles = &staticGles;
1153
+ #elif defined(_WIN32)
1084
1154
  // An override (e.g. threejs-rendozer's GeaRendozerGLES.dll) first, then the
1085
1155
  // packaged ANGLE of a UWP app, then a plain desktop load next to the exe.
1086
1156
  auto openWin32 = [](const wchar_t *overrideEnv, const wchar_t *name) -> HMODULE {
@@ -1097,7 +1167,7 @@ extern "C" bool gea_three_webgl_attach(
1097
1167
  };
1098
1168
  gWebGL.egl = openWin32(L"GEA_ANGLE_EGL_DYLIB", L"libEGL.dll");
1099
1169
  gWebGL.gles = openWin32(L"GEA_ANGLE_GLES_DYLIB", L"libGLESv2.dll");
1100
- #else
1170
+ #elif defined(__APPLE__)
1101
1171
  static const char *const eglPaths[] = {
1102
1172
  "/Applications/Visual Studio Code.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib",
1103
1173
  "/Applications/Cursor.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libEGL.dylib",
@@ -1115,6 +1185,8 @@ extern "C" bool gea_three_webgl_attach(
1115
1185
 
1116
1186
  gWebGL.egl = openFirst("GEA_ANGLE_EGL_DYLIB", eglPaths);
1117
1187
  gWebGL.gles = openFirst("GEA_ANGLE_GLES_DYLIB", glesPaths);
1188
+ #else
1189
+ #error "load libEGL/libGLESv2 for this platform, or build with GEA_WEBGL_STATIC_GLES"
1118
1190
  #endif
1119
1191
  if (!gWebGL.egl || !gWebGL.gles) {
1120
1192
  smokeLog("[three-angle-metal] ANGLE libEGL/libGLESv2 were not found");
@@ -1192,7 +1264,7 @@ extern "C" bool gea_three_webgl_attach(
1192
1264
  }
1193
1265
  gWebGL.config = config;
1194
1266
 
1195
- #if defined(_WIN32)
1267
+ #if !defined(__APPLE__)
1196
1268
  gWebGL.nativeWindow = nativeWindow;
1197
1269
  gea_three_webgl_update_size_uwp(width, height, devicePixelRatio);
1198
1270
  if (gWebGL.eglCreateWindowSurface) {
@@ -1258,7 +1330,7 @@ extern "C" bool gea_three_webgl_attach(
1258
1330
 
1259
1331
  gWebGL.ready = true;
1260
1332
  gWebGL.glViewport(0, 0, gWebGL.widthPx, gWebGL.heightPx);
1261
- #if !defined(_WIN32)
1333
+ #if defined(__APPLE__)
1262
1334
  dispatch_async(dispatch_get_main_queue(), ^{
1263
1335
  if (gWebGL.ready && rebuildWindowSurface("eglCreateWindowSurface(deferred)")) {
1264
1336
  smokeLog("[three-angle-metal] WebGL EGL surface rebound at %dx%d", gWebGL.widthPx, gWebGL.heightPx);
@@ -1374,7 +1446,7 @@ extern "C" void gea_three_webgl_swap() {
1374
1446
  // TEMP-DIAG: GEA_THREE_VIEW_DUMP=1 — at frame 60, log the window's full
1375
1447
  // subview tree and a hit-test grid so overlay views that would swallow
1376
1448
  // mouse events are visible without Accessibility permission.
1377
- #if !defined(_WIN32)
1449
+ #if defined(__APPLE__)
1378
1450
  if (std::getenv("GEA_THREE_VIEW_DUMP")) {
1379
1451
  static int viewDumpCount = 0;
1380
1452
  if (++viewDumpCount == 60) {
@@ -1407,6 +1479,16 @@ extern "C" void gea_three_webgl_swap() {
1407
1479
  }
1408
1480
  }
1409
1481
  #endif
1482
+ // The debug dump and recording read the window into client memory. A PIXEL_PACK_BUFFER the app left
1483
+ // bound (WebGL2 asynchronous readback) would make the pointer a buffer offset instead, so the read
1484
+ // runs with it unbound and the binding is restored afterwards.
1485
+ const auto readWindowRgba = [](int w, int h, unsigned char *rgba) {
1486
+ GLint packBuffer = 0;
1487
+ if (gWebGL.glGetIntegerv) gWebGL.glGetIntegerv(0x88ED /* PIXEL_PACK_BUFFER_BINDING */, &packBuffer);
1488
+ if (packBuffer) gWebGL.glBindBuffer(0x88EB /* PIXEL_PACK_BUFFER */, 0);
1489
+ gWebGL.glReadPixels(0, 0, w, h, GL_RGBA_VALUE, GL_UNSIGNED_BYTE, rgba);
1490
+ if (packBuffer) gWebGL.glBindBuffer(0x88EB, static_cast<GLuint>(packBuffer));
1491
+ };
1410
1492
  // Debug frame dump: GEA_WEBGL_DUMP_FRAME=<path-prefix> writes the full
1411
1493
  // framebuffer of frames 90/180/300 as PPM (P6) — visual ground truth for
1412
1494
  // native-vs-browser comparisons without needing a window screenshot.
@@ -1417,7 +1499,7 @@ extern "C" void gea_three_webgl_swap() {
1417
1499
  const int w = gWebGL.widthPx;
1418
1500
  const int h = gWebGL.heightPx;
1419
1501
  std::vector<unsigned char> rgba(static_cast<size_t>(w) * h * 4);
1420
- gWebGL.glReadPixels(0, 0, w, h, GL_RGBA_VALUE, GL_UNSIGNED_BYTE, rgba.data());
1502
+ readWindowRgba(w, h, rgba.data());
1421
1503
  char path[512];
1422
1504
  std::snprintf(path, sizeof path, "%s-f%d.ppm", dumpPrefix, dumpCount);
1423
1505
  if (FILE *file = std::fopen(path, "wb")) {
@@ -1466,7 +1548,7 @@ extern "C" void gea_three_webgl_swap() {
1466
1548
  if (due > recordWritten && gWebGL.widthPx == recordW && gWebGL.heightPx == recordH) {
1467
1549
  std::vector<unsigned char> rgba(static_cast<size_t>(recordW) * recordH * 4);
1468
1550
  std::vector<unsigned char> rgb(static_cast<size_t>(recordW) * recordH * 3);
1469
- gWebGL.glReadPixels(0, 0, recordW, recordH, GL_RGBA_VALUE, GL_UNSIGNED_BYTE, rgba.data());
1551
+ readWindowRgba(recordW, recordH, rgba.data());
1470
1552
  for (int y = 0; y < recordH; ++y) {
1471
1553
  const unsigned char *src = rgba.data() + static_cast<size_t>(recordH - 1 - y) * recordW * 4;
1472
1554
  unsigned char *dst = rgb.data() + static_cast<size_t>(y) * recordW * 3;
@@ -2259,7 +2341,7 @@ extern "C" void gea_three_webgl_draw_arrays_instanced(double mode, double first,
2259
2341
  geaTrapCheck("gea_three_webgl_draw_arrays_instanced"); GEA_WEBGL_VOID("drawArraysInstanced");
2260
2342
  gWebGL.glDrawArraysInstanced(asGLenum(mode), asGLint(first), asGLsizei(count), asGLsizei(instanceCount)); }
2261
2343
  extern "C" double gea_three_webgl_get_error() {
2262
- geaTrapCheck("gea_three_webgl_get_error"); GEA_WEBGL_DOUBLE("getError"); return gWebGL.glGetError(); }
2344
+ geaTrapCheck("gea_three_webgl_get_error"); GEA_WEBGL_DOUBLE("getError"); if (hostReadbackError) { const GLenum e = hostReadbackError; hostReadbackError = 0; return e; } return gWebGL.glGetError(); }
2263
2345
 
2264
2346
  extern "C" double gea_three_webgl_call(
2265
2347
  double opValue,
@@ -2481,6 +2563,36 @@ extern "C" void gea_three_webgl_tex_image_2d_bytes(double target, double level,
2481
2563
  gWebGL.glTexImage2D(asGLenum(target), asGLint(level), asGLint(internalFormat), asGLsizei(width), asGLsizei(height), 0, asGLenum(format), asGLenum(type), bytes);
2482
2564
  }
2483
2565
 
2566
+ static bool requireGl(const void *fn, const char *name) {
2567
+ if (fn) return true;
2568
+ smokeLog("[native-webgl] %s is not provided by this GLES; the call is dropped", name);
2569
+ return false;
2570
+ }
2571
+
2572
+ 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) {
2573
+ GEA_WEBGL_VOID("texImage3DBytes");
2574
+ if (!requireGl(reinterpret_cast<const void *>(gWebGL.glTexImage3D), "glTexImage3D")) return;
2575
+ gWebGL.glTexImage3D(asGLenum(target), asGLint(level), asGLint(internalFormat), asGLsizei(width), asGLsizei(height), asGLsizei(depth), 0, asGLenum(format), asGLenum(type), bytes);
2576
+ }
2577
+
2578
+ 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) {
2579
+ GEA_WEBGL_VOID("texSubImage3DBytes");
2580
+ if (!requireGl(reinterpret_cast<const void *>(gWebGL.glTexSubImage3D), "glTexSubImage3D")) return;
2581
+ gWebGL.glTexSubImage3D(asGLenum(target), asGLint(level), asGLint(xoffset), asGLint(yoffset), asGLint(zoffset), asGLsizei(width), asGLsizei(height), asGLsizei(depth), asGLenum(format), asGLenum(type), bytes);
2582
+ }
2583
+
2584
+ extern "C" void gea_three_webgl_tex_storage_3d(double target, double levels, double internalFormat, double width, double height, double depth) {
2585
+ GEA_WEBGL_VOID("texStorage3D");
2586
+ if (!requireGl(reinterpret_cast<const void *>(gWebGL.glTexStorage3D), "glTexStorage3D")) return;
2587
+ gWebGL.glTexStorage3D(asGLenum(target), asGLsizei(std::max(1.0, levels)), asGLenum(internalFormat), asGLsizei(width), asGLsizei(height), asGLsizei(depth));
2588
+ }
2589
+
2590
+ extern "C" void gea_three_webgl_framebuffer_texture_layer(double target, double attachment, double texture, double level, double layer) {
2591
+ GEA_WEBGL_VOID("framebufferTextureLayer");
2592
+ if (!requireGl(reinterpret_cast<const void *>(gWebGL.glFramebufferTextureLayer), "glFramebufferTextureLayer")) return;
2593
+ gWebGL.glFramebufferTextureLayer(asGLenum(target), asGLenum(attachment), asGLuint(texture), asGLint(level), asGLint(layer));
2594
+ }
2595
+
2484
2596
  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) {
2485
2597
  GEA_WEBGL_VOID("texSubImage2DBytes");
2486
2598
  gWebGL.glTexSubImage2D(asGLenum(target), asGLint(level), asGLint(xoffset), asGLint(yoffset), asGLsizei(width), asGLsizei(height), asGLenum(format), asGLenum(type), bytes);
@@ -2771,3 +2883,135 @@ extern "C" double gea_three_webgl_call_f32(
2771
2883
  }
2772
2884
  return 0.0;
2773
2885
  }
2886
+
2887
+ extern "C" void gea_three_webgl_read_pixels_bytes(double x, double y, double width, double height,
2888
+ double format, double type, void* bytes, double byteLength, double elementType) {
2889
+ GEA_WEBGL_VOID("readPixels");
2890
+ GLint alignment = 4, rowLength = 0, skipRows = 0, skipPixels = 0, packBuffer = 0;
2891
+ gWebGL.glGetIntegerv(0x0D05, &alignment);
2892
+ gWebGL.glGetIntegerv(0x0D02, &rowLength);
2893
+ gWebGL.glGetIntegerv(0x0D03, &skipRows);
2894
+ gWebGL.glGetIntegerv(0x0D04, &skipPixels);
2895
+ gWebGL.glGetIntegerv(0x88ED, &packBuffer);
2896
+ GLenum error = gea_webgl::validateReadPixels(width,height,format,type,byteLength,elementType,alignment,rowLength,skipRows,skipPixels);
2897
+ if (packBuffer || (!bytes && width > 0 && height > 0)) error = 0x0502;
2898
+ if (!std::isfinite(x) || !std::isfinite(y) || x < INT32_MIN || x > INT32_MAX || y < INT32_MIN || y > INT32_MAX) error = 0x0501;
2899
+ if (error) { if (!hostReadbackError) hostReadbackError = error; return; }
2900
+ gWebGL.glReadPixels(asGLint(x),asGLint(y),asGLsizei(width),asGLsizei(height),asGLenum(format),asGLenum(type),bytes);
2901
+ }
2902
+
2903
+ // WebGL2 asynchronous readback (three's readRenderTargetPixelsAsync): readPixels into the bound
2904
+ // PIXEL_PACK_BUFFER at a byte offset, a fence, polling, then getBufferSubData through a read mapping.
2905
+ extern "C" void gea_three_webgl_read_pixels_pack(double x, double y, double width, double height, double format, double type, double offset) {
2906
+ GEA_WEBGL_VOID("readPixels");
2907
+ GLint packBuffer = 0;
2908
+ gWebGL.glGetIntegerv(0x88ED, &packBuffer);
2909
+ if (!packBuffer || !std::isfinite(offset) || offset < 0 || std::floor(offset) != offset) { if (!hostReadbackError) hostReadbackError = 0x0502; return; }
2910
+ gWebGL.glReadPixels(asGLint(x), asGLint(y), asGLsizei(width), asGLsizei(height), asGLenum(format), asGLenum(type),
2911
+ reinterpret_cast<void *>(static_cast<std::uintptr_t>(offset)));
2912
+ }
2913
+ static std::unordered_map<std::uint32_t, GLsync> gWebGLSyncs;
2914
+ static std::uint32_t gWebGLNextSync = 1;
2915
+ extern "C" double gea_three_webgl_fence_sync() {
2916
+ GEA_WEBGL_DOUBLE("fenceSync");
2917
+ GLsync sync = gWebGL.glFenceSync(0x9117 /* SYNC_GPU_COMMANDS_COMPLETE */, 0);
2918
+ if (!sync) return 0.0;
2919
+ const std::uint32_t id = gWebGLNextSync++;
2920
+ gWebGLSyncs[id] = sync;
2921
+ return static_cast<double>(id);
2922
+ }
2923
+ extern "C" double gea_three_webgl_client_wait_sync(double sync, double flags, double timeout) {
2924
+ GEA_WEBGL_DOUBLE("clientWaitSync");
2925
+ auto it = gWebGLSyncs.find(static_cast<std::uint32_t>(sync));
2926
+ if (it == gWebGLSyncs.end()) return 0x911D; // WAIT_FAILED
2927
+ return static_cast<double>(gWebGL.glClientWaitSync(it->second, static_cast<GLbitfield>(flags), static_cast<GLuint64>(std::max(0.0, timeout))));
2928
+ }
2929
+ extern "C" void gea_three_webgl_delete_sync(double sync) {
2930
+ GEA_WEBGL_VOID("deleteSync");
2931
+ auto it = gWebGLSyncs.find(static_cast<std::uint32_t>(sync));
2932
+ if (it == gWebGLSyncs.end()) return;
2933
+ gWebGL.glDeleteSync(it->second);
2934
+ gWebGLSyncs.erase(it);
2935
+ }
2936
+ // GPU timers for the app's frame profiler, on GL_EXT_disjoint_timer_query.
2937
+ struct HostGpuTimers {
2938
+ bool initialized = false, supported = false;
2939
+ void (*gen)(GLsizei, GLuint *) = nullptr;
2940
+ void (*remove)(GLsizei, const GLuint *) = nullptr;
2941
+ void (*begin)(GLenum, GLuint) = nullptr;
2942
+ void (*end)(GLenum) = nullptr;
2943
+ void (*available)(GLuint, GLenum, GLuint *) = nullptr;
2944
+ void (*result)(GLuint, GLenum, std::uint64_t *) = nullptr;
2945
+ bool open = false;
2946
+ bool ensure() {
2947
+ if (initialized) return supported;
2948
+ initialized = true;
2949
+ const auto extensions = reinterpret_cast<const char *>(gWebGL.glGetString(0x1F03));
2950
+ const std::string list = " " + std::string(extensions ? extensions : "") + " ";
2951
+ if (list.find(" GL_EXT_disjoint_timer_query ") == std::string::npos) return false;
2952
+ gen = loadGl<decltype(gen)>("glGenQueriesEXT");
2953
+ remove = loadGl<decltype(remove)>("glDeleteQueriesEXT");
2954
+ begin = loadGl<decltype(begin)>("glBeginQueryEXT");
2955
+ end = loadGl<decltype(end)>("glEndQueryEXT");
2956
+ available = loadGl<decltype(available)>("glGetQueryObjectuivEXT");
2957
+ result = loadGl<decltype(result)>("glGetQueryObjectui64vEXT");
2958
+ supported = gen && remove && begin && end && available && result;
2959
+ return supported;
2960
+ }
2961
+ };
2962
+ static HostGpuTimers gHostGpuTimers;
2963
+ extern "C" double gea_three_webgl_timer_begin() {
2964
+ GEA_WEBGL_DOUBLE("timerBegin");
2965
+ auto &t = gHostGpuTimers;
2966
+ if (!t.ensure() || t.open) return 0.0;
2967
+ GLuint query = 0;
2968
+ t.gen(1, &query);
2969
+ if (!query) return 0.0;
2970
+ t.begin(0x88BF /* TIME_ELAPSED_EXT */, query);
2971
+ t.open = true;
2972
+ return static_cast<double>(query);
2973
+ }
2974
+ extern "C" void gea_three_webgl_timer_end() {
2975
+ GEA_WEBGL_VOID("timerEnd");
2976
+ auto &t = gHostGpuTimers;
2977
+ if (!t.supported || !t.open) return;
2978
+ t.end(0x88BF);
2979
+ t.open = false;
2980
+ }
2981
+ extern "C" double gea_three_webgl_timer_result(double timer) {
2982
+ GEA_WEBGL_DOUBLE("timerResult");
2983
+ auto &t = gHostGpuTimers;
2984
+ const GLuint query = static_cast<GLuint>(timer);
2985
+ if (!t.supported || !query) return 0.0;
2986
+ GLuint ready = 0;
2987
+ t.available(query, 0x8867 /* QUERY_RESULT_AVAILABLE */, &ready);
2988
+ if (!ready) return -1.0;
2989
+ std::uint64_t ns = 0;
2990
+ t.result(query, 0x8866 /* QUERY_RESULT */, &ns);
2991
+ t.remove(1, &query);
2992
+ return static_cast<double>(ns);
2993
+ }
2994
+ extern "C" void gea_three_webgl_get_buffer_sub_data_bytes(double target, double srcOffset, void *bytes, double byteLength) {
2995
+ GEA_WEBGL_VOID("getBufferSubData");
2996
+ if (!bytes || !(byteLength > 0) || !(srcOffset >= 0)) { if (!hostReadbackError) hostReadbackError = 0x0501; return; }
2997
+ void *mapped = gWebGL.glMapBufferRange(asGLenum(target), static_cast<GLintptr>(srcOffset), static_cast<GLsizeiptr>(byteLength), 0x0001 /* MAP_READ_BIT */);
2998
+ if (!mapped) { if (!hostReadbackError) hostReadbackError = 0x0502; return; }
2999
+ std::memcpy(bytes, mapped, static_cast<std::size_t>(byteLength));
3000
+ gWebGL.glUnmapBuffer(asGLenum(target));
3001
+ }
3002
+
3003
+ 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) {
3004
+ GEA_WEBGL_VOID("blitFramebuffer");
3005
+ gWebGL.glBlitFramebuffer(asGLint(sx0),asGLint(sy0),asGLint(sx1),asGLint(sy1),asGLint(dx0),asGLint(dy0),asGLint(dx1),asGLint(dy1),asGLenum(mask),asGLenum(filter));
3006
+ }
3007
+ extern "C" void gea_three_webgl_renderbuffer_storage_multisample(double target,double samples,double format,double width,double height) {
3008
+ GEA_WEBGL_VOID("renderbufferStorageMultisample");
3009
+ gWebGL.glRenderbufferStorageMultisample(asGLenum(target),asGLsizei(samples),asGLenum(format),asGLsizei(width),asGLsizei(height));
3010
+ }
3011
+
3012
+ extern "C" void gea_three_webgl_copy_tex_image_2d(double target,double level,double format,double x,double y,double w,double h,double border){
3013
+ GEA_WEBGL_VOID("copyTexImage2D");gWebGL.glCopyTexImage2D(asGLenum(target),asGLint(level),asGLenum(format),asGLint(x),asGLint(y),asGLsizei(w),asGLsizei(h),asGLint(border));
3014
+ }
3015
+ extern "C" void gea_three_webgl_copy_tex_sub_image_2d(double target,double level,double xo,double yo,double x,double y,double w,double h){
3016
+ GEA_WEBGL_VOID("copyTexSubImage2D");gWebGL.glCopyTexSubImage2D(asGLenum(target),asGLint(level),asGLint(xo),asGLint(yo),asGLint(x),asGLint(y),asGLsizei(w),asGLsizei(h));
3017
+ }
@@ -0,0 +1,29 @@
1
+ #pragma once
2
+ #include <cmath>
3
+ #include <cstdint>
4
+ #include <limits>
5
+
6
+ namespace gea_webgl {
7
+ // Calculate the complete destination window, including WebGL2 pack layout.
8
+ // Return a GL error before the driver can touch an undersized typed array.
9
+ inline unsigned validateReadPixels(double width, double height, double format, double type,
10
+ double bytes, double elementType, int alignment, int rowLength, int skipRows, int skipPixels) {
11
+ if (!std::isfinite(width) || !std::isfinite(height) || width < 0 || height < 0 ||
12
+ width > INT32_MAX || height > INT32_MAX || std::floor(width) != width || std::floor(height) != height) return 0x0501;
13
+ if (format != 0x1908 || (type != 0x1401 && type != 0x1406 && type != 0x140B)) return 0x0502;
14
+ if (elementType != type && !(type == 0x140B && elementType == 0x1403)) return 0x0502;
15
+ if (bytes < 0 || !std::isfinite(bytes)) return 0x0501;
16
+ if (width == 0 || height == 0) return 0;
17
+ if (alignment != 1 && alignment != 2 && alignment != 4 && alignment != 8) return 0x0502;
18
+ if (rowLength < 0 || skipRows < 0 || skipPixels < 0) return 0x0502;
19
+ const uint64_t pixel = type == 0x1406 ? 16 : type == 0x140B ? 8 : 4;
20
+ const uint64_t row = rowLength ? rowLength : static_cast<uint64_t>(width);
21
+ if (row < static_cast<uint64_t>(width) + skipPixels) return 0x0502;
22
+ const uint64_t pitch = (row*pixel + alignment-1)/alignment*alignment;
23
+ const uint64_t rows = static_cast<uint64_t>(height)-1+skipRows;
24
+ if (rows && pitch > UINT64_MAX/rows) return 0x0502;
25
+ const uint64_t tail = (static_cast<uint64_t>(skipPixels)+static_cast<uint64_t>(width))*pixel;
26
+ if (rows*pitch > UINT64_MAX-tail) return 0x0502;
27
+ return static_cast<long double>(rows*pitch+tail) <= bytes ? 0 : 0x0502;
28
+ }
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geastack/native-webgl-angle",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Typed native WebGL compatibility layer backed by ANGLE/Metal",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -20,6 +20,7 @@
20
20
  "./nativeThreeUtils": "./src/nativeThreeUtils.ts",
21
21
  "./nativeWebXRManager": "./src/nativeWebXRManager.ts",
22
22
  "./troika-three-text": "./src/troika-three-text.ts",
23
+ "./inflate": "./src/inflate.ts",
23
24
  "./geatsc-plugin": "./geatsc-plugin.mjs",
24
25
  "./native/*": "./native/*",
25
26
  "./package.json": "./package.json",
@@ -38,7 +39,8 @@
38
39
  "geatsc-plugin-instancing.mjs",
39
40
  "geatsc-plugin-batched-probe.mjs",
40
41
  "test/batched-render-scene.ts",
41
- "test/auto-instanced-render-scene.ts"
42
+ "test/auto-instanced-render-scene.ts",
43
+ "geatsc-plugin-readback.mjs"
42
44
  ],
43
45
  "publishConfig": {
44
46
  "access": "public",
@@ -53,16 +55,16 @@
53
55
  "test:package": "node --experimental-vm-modules test/package.test.mjs"
54
56
  },
55
57
  "dependencies": {
56
- "@geastack/apple": "^0.2.14"
58
+ "@geastack/apple": "^0.2.15"
57
59
  },
58
60
  "peerDependencies": {
59
61
  "three": "^0.185.0",
60
- "@geastack/compiler": "^1.0.22"
62
+ "@geastack/compiler": "^1.0.29"
61
63
  },
62
64
  "devDependencies": {
63
- "@geastack/compiler": "^1.0.22",
64
- "@geastack/core": "^0.1.37",
65
- "@geastack/engine": "^0.1.13",
65
+ "@geastack/compiler": "^1.0.29",
66
+ "@geastack/core": "^0.1.40",
67
+ "@geastack/engine": "^0.1.16",
66
68
  "@types/three": "^0.185.0",
67
69
  "three": "^0.185.0",
68
70
  "troika-three-text": "^0.52.4",