From cbbc01e76c5068717f08dffcfd4989af57617712 Mon Sep 17 00:00:00 2001 From: Jamie Madill Date: Tue, 29 Apr 2025 11:46:31 -0400 Subject: [PATCH] Minor improvements for WebGL 2 and development. - Basic support for texSubImage3D. - Code support for Renderdoc, which is enabled via a define. - Fix for nodejs issue with detecting instances of buffer types. - Simple conversion for RGBA->RED pixel formats on texture upload. --- src/javascript/utils.js | 52 +++++++++++++------ src/javascript/webgl-rendering-context.js | 27 ++++++++++ src/native/webgl.cc | 62 +++++++++++++++++++---- 3 files changed, 115 insertions(+), 26 deletions(-) diff --git a/src/javascript/utils.js b/src/javascript/utils.js index 91af5253..61a3c182 100644 --- a/src/javascript/utils.js +++ b/src/javascript/utils.js @@ -26,6 +26,10 @@ function checkObject (object) { (object === undefined) } +function isInstanceOfType (instance, type) { + return Object.prototype.toString.call(instance) === `[object ${type}]` +} + function checkUniform (program, location) { return location instanceof WebGLUniformLocation && location._program === program && @@ -33,15 +37,15 @@ function checkUniform (program, location) { } function isTypedArray (data) { - return data instanceof Uint8Array || - data instanceof Uint8ClampedArray || - data instanceof Int8Array || - data instanceof Uint16Array || - data instanceof Int16Array || - data instanceof Uint32Array || - data instanceof Int32Array || - data instanceof Float32Array || - data instanceof Float64Array + return isInstanceOfType(data, 'Uint8Array') || + isInstanceOfType(data, 'Uint8ClampedArray') || + isInstanceOfType(data, 'Int8Array') || + isInstanceOfType(data, 'Uint16Array') || + isInstanceOfType(data, 'Int16Array') || + isInstanceOfType(data, 'Uint32Array') || + isInstanceOfType(data, 'Int32Array') || + isInstanceOfType(data, 'Float32Array') || + isInstanceOfType(data, 'Float64Array') } // Don't allow: ", $, `, @, \, ', \0 @@ -159,6 +163,23 @@ function extractImageData (pixels) { return null } +function convertPixelFormats (ctx, pixels, srcFormat, dstFormat) { + switch (srcFormat) { + case ctx.RGBA: + switch (dstFormat) { + case ctx.RGBA: + return pixels + case ctx.RED: + // extract the red channel from pixels, which is in typed array format, and convert to Uint8Array + return new Uint8Array(pixels.filter((_, i) => i % 4 === 0)) + default: + throw new Error('unsupported destination format') + } + default: + throw new Error('unsupported source format') + } +} + function formatSize (internalFormat) { switch (internalFormat) { case gl.ALPHA: @@ -176,14 +197,14 @@ function formatSize (internalFormat) { function convertPixels (pixels) { if (typeof pixels === 'object' && pixels !== null) { - if (pixels instanceof ArrayBuffer) { + if (isInstanceOfType(pixels, 'ArrayBuffer')) { return new Uint8Array(pixels) - } else if (pixels instanceof Uint8Array || - pixels instanceof Uint16Array || - pixels instanceof Uint8ClampedArray || - pixels instanceof Float32Array) { + } else if (isInstanceOfType(pixels, 'Uint8Array') || + isInstanceOfType(pixels, 'Uint16Array') || + isInstanceOfType(pixels, 'Uint8ClampedArray') || + isInstanceOfType(pixels, 'Float32Array')) { return unpackTypedArray(pixels) - } else if (pixels instanceof Buffer) { + } else if (isInstanceOfType(pixels, 'Buffer')) { return new Uint8Array(pixels) } } @@ -218,6 +239,7 @@ module.exports = { uniformTypeSize, unpackTypedArray, extractImageData, + convertPixelFormats, formatSize, checkFormat, checkUniform, diff --git a/src/javascript/webgl-rendering-context.js b/src/javascript/webgl-rendering-context.js index de011e9c..f254330e 100644 --- a/src/javascript/webgl-rendering-context.js +++ b/src/javascript/webgl-rendering-context.js @@ -20,6 +20,7 @@ const { typeSize, uniformTypeSize, extractImageData, + convertPixelFormats, isTypedArray, unpackTypedArray, convertPixels, @@ -2296,6 +2297,8 @@ class WebGLRenderingContextHelper extends NativeWebGLRenderingContext { width = pixels.width height = pixels.height pixels = pixels.data + + pixels = convertPixelFormats(this, pixels, this.RGBA, format) } target |= 0 @@ -2366,6 +2369,8 @@ class WebGLRenderingContextHelper extends NativeWebGLRenderingContext { width = pixels.width height = pixels.height pixels = pixels.data + + pixels = convertPixelFormats(this, pixels, this.RGBA, format) } if (typeof pixels !== 'object') { @@ -2386,6 +2391,28 @@ class WebGLRenderingContextHelper extends NativeWebGLRenderingContext { data) } + texSubImage3D (target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels) { + if (pixels === null || pixels === undefined) { + return + } + + if (typeof pixels !== 'object') { + throw new TypeError('texSubImage3D(GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLenum, GLenum, Uint8Array)') + } + + if ( + typeof pixels === 'object' && + typeof pixels.width !== 'undefined' && + typeof pixels.height !== 'undefined' + ) { + pixels = extractImageData(pixels).data + pixels = convertPixelFormats(this, pixels, this.RGBA, format) + } + const data = convertPixels(pixels) + + super.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data) + } + texParameterf (target, pname, param) { target |= 0 pname |= 0 diff --git a/src/native/webgl.cc b/src/native/webgl.cc index 11a5fd24..20f62af4 100644 --- a/src/native/webgl.cc +++ b/src/native/webgl.cc @@ -7,6 +7,12 @@ #include "webgl.h" +#if defined(RENDERDOC_ENABLED) +#include "renderdoc_app.h" + +RENDERDOC_API_1_1_2 *rdoc_api = NULL; +#endif + const char *GetDebugMessageSourceString(GLenum source) { switch (source) { case GL_DEBUG_SOURCE_API: @@ -143,6 +149,19 @@ std::string JoinStringSet(const std::set &inputSet, return oss.str(); } +// Helper function to potentially end RenderDoc frame capture after a few draw calls +#if defined(RENDERDOC_ENABLED) +static void MaybeEndRenderDocCapture() { + static int drawCallCounter = 0; + if (drawCallCounter++ == 100 && rdoc_api) { + rdoc_api->EndFrameCapture(NULL, NULL); + printf("RenderDoc capture ended after %d draw calls.\n", drawCallCounter); + drawCallCounter = 0; + rdoc_api->StartFrameCapture(NULL, NULL); + } +} +#endif + bool WebGLRenderingContext::HAS_DISPLAY = false; EGLDisplay WebGLRenderingContext::DISPLAY; WebGLRenderingContext *WebGLRenderingContext::ACTIVE = NULL; @@ -191,6 +210,23 @@ WebGLRenderingContext::WebGLRenderingContext(int width, int height, bool alpha, unpack_colorspace_conversion(0x9244), unpack_alignment(4), webGLToANGLEExtensions(&CaseInsensitiveCompare), next(NULL), prev(NULL) { + // Uncomment on Windows to attach debugger + // MessageBox(NULL, "Hello", "Hello", MB_OK); + +#if defined(RENDERDOC_ENABLED) + if (!rdoc_api) { + if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) { + pRENDERDOC_GetAPI RENDERDOC_GetAPI = + (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI"); + int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void **)&rdoc_api); + printf("RENDERDOC_GetAPI ret: %d %p\n", ret, rdoc_api); + assert(ret == 1); + } else { + printf("renderdoc.dll not found\n"); + } + } +#endif + if (!eglGetProcAddress) { if (!eglLibrary.open("libEGL")) { errorMessage = "Error opening ANGLE shared library."; @@ -342,6 +378,12 @@ WebGLRenderingContext::WebGLRenderingContext(int width, int height, bool alpha, supportedWebGLExtensions.insert(webGLExtension); } } + +#if defined(RENDERDOC_ENABLED) + if (rdoc_api) { + rdoc_api->StartFrameCapture(NULL, NULL); + } +#endif } bool WebGLRenderingContext::setActive() { @@ -1056,6 +1098,10 @@ GL_METHOD(TexSubImage2D) { Nan::TypedArrayContents pixels(info[8]); if (inst->unpack_flip_y || inst->unpack_premultiply_alpha) { + if (!*pixels) { + inst->setError(GL_INVALID_OPERATION); + return; + } std::vector unpacked = inst->unpackPixels(type, format, width, height, *pixels); glTexSubImage2DRobustANGLE(target, level, xoffset, yoffset, width, height, format, type, unpacked.size(), unpacked.data()); @@ -2410,6 +2456,7 @@ GL_METHOD(TexImage3D) { GLint border = Nan::To(info[6]).ToChecked(); GLenum format = Nan::To(info[7]).ToChecked(); GLenum type = Nan::To(info[8]).ToChecked(); + if (info[9]->IsUndefined()) { glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, nullptr); @@ -2435,17 +2482,10 @@ GL_METHOD(TexSubImage3D) { GLsizei depth = Nan::To(info[7]).ToChecked(); GLenum format = Nan::To(info[8]).ToChecked(); GLenum type = Nan::To(info[9]).ToChecked(); - if (info[10]->IsUndefined()) { - glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, - nullptr); - } else if (info[10]->IsArrayBufferView()) { - auto buffer = info[10].As(); - void *bufferPtr = buffer->Buffer()->GetBackingStore()->Data(); - glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, - bufferPtr); - } else { - Nan::ThrowTypeError("Invalid data type for TexSubImage3D"); - } + Nan::TypedArrayContents pixels(info[10]); + + glTexSubImage3DRobustANGLE(target, level, xoffset, yoffset, zoffset, width, height, depth, format, + type, pixels.length(), *pixels); } GL_METHOD(CopyTexSubImage3D) {