From db2f5011432c373aae6ac12e5184a58c33b6eccd Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Sat, 24 Oct 2026 06:13:49 -0400 Subject: [PATCH] fix(windows): WebGL2's sized texImage2D/texSubImage2D take a canvas or image WebGL2 lets the sized forms, texImage2D(target, level, internalformat, width, height, border, format, type, source) and texSubImage2D(target, level, x, y, width, height, format, type, source), take an image, canvas, video, bitmap or ImageData. The Node-API binding takes sources only in the unsized forms, so on Windows these threw InvalidArg ("Value is none of these types ArrayBuffer | ArrayBufferView, i64"). PixiJS uploads every Text this way, so text never rendered. On Node-API hosts a source in the sized form now goes through the unsized form, which sizes the texture from the source as the sized one does. --- .../WebGL/WebGLRenderingContext/index.ts | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/canvas/WebGL/WebGLRenderingContext/index.ts b/packages/canvas/WebGL/WebGLRenderingContext/index.ts index 44fe1b75..1a37faae 100644 --- a/packages/canvas/WebGL/WebGLRenderingContext/index.ts +++ b/packages/canvas/WebGL/WebGLRenderingContext/index.ts @@ -46,6 +46,11 @@ import { NAPI_HOST, POINTER_CONTEXT_HOST } from '../../platform'; import { imageAssetFor } from '../../platform/index'; import { isCanvasView, isOffscreenCanvas } from '../../Canvas/utils'; +/** An image, canvas, video, bitmap or ImageData: not pixels in a buffer, an offset or null. */ +function isTexImageSource(value: unknown): boolean { + return !!value && typeof value === 'object' && !ArrayBuffer.isView(value) && !(value instanceof ArrayBuffer); +} + enum ContextType { None, Canvas, @@ -893,7 +898,11 @@ export class WebGLRenderingContextBase extends WebGLRenderingCommon { const length = arguments.length; if (length === 9) { pixels = fromSvgSource(pixels); - if (pixels && typeof pixels.tagName === 'string' && (pixels.tagName === 'VID' || pixels.tagName === 'VIDEO') && pixels._video && typeof pixels._video.getCurrentFrame === 'function') { + if (NAPI_HOST && isTexImageSource(pixels)) { + // WebGL2's sized form with an image or canvas (PixiJS uploads text this way). Node-API hosts take + // sources in the unsized form only, which sizes the texture from the source as the sized one does. + this.texImage2D(target, level, internalformat, format, type ?? internalformat, pixels); + } else if (pixels && typeof pixels.tagName === 'string' && (pixels.tagName === 'VID' || pixels.tagName === 'VIDEO') && pixels._video && typeof pixels._video.getCurrentFrame === 'function') { pixels._video.getCurrentFrame(this.native, this, target, level, internalformat, width, height); } else if (pixels && typeof pixels.getCurrentFrame === 'function') { pixels.getCurrentFrame(this.native, this, target, level, internalformat, width, height); @@ -903,7 +912,7 @@ export class WebGLRenderingContextBase extends WebGLRenderingCommon { this.native.texImage2D(target, level, internalformat, width, height, border, format, type ?? internalformat, pixels.native); } else if (pixels instanceof ImageBitmap) { this.native.texImage2D(target, level, internalformat, width, height, border, format, type ?? internalformat, pixels.native); - } else if ((isCanvasView(pixels) || isOffscreenCanvas(pixels))) { + } else if (isCanvasView(pixels) || isOffscreenCanvas(pixels)) { this.native.texImage2D(target, level, internalformat, width, height, border, format, type ?? internalformat, pixels.native); } else if (__ANDROID__ && pixels instanceof android.graphics.Bitmap) { (org).nativescript.canvas.NSCWebGLRenderingContext.texImage2D(java.lang.Long.valueOf(this._contextPtr), target, level, internalformat, width, height, pixels, this.native.__flipY); @@ -959,7 +968,7 @@ export class WebGLRenderingContextBase extends WebGLRenderingCommon { this.native.texImage2D(target, level, internalformat, width, height, border.native); } else if (border instanceof ImageBitmap) { this.native.texImage2D(target, level, internalformat, width, height, border.native); - } else if ((isCanvasView(border) || isOffscreenCanvas(border))) { + } else if (isCanvasView(border) || isOffscreenCanvas(border)) { this.native.texImage2D(target, level, internalformat, width, height, border.native); } else if (__ANDROID__ && border instanceof android.graphics.Bitmap) { (org).nativescript.canvas.NSCWebGLRenderingContext.texImage2D(java.lang.Long.valueOf(this._contextPtr), target, level, internalformat, width, height, border, this.native.__flipY); @@ -1012,7 +1021,11 @@ export class WebGLRenderingContextBase extends WebGLRenderingCommon { texSubImage2D(target: any, level: any, xoffset: any, yoffset: any, width: any, height: any, format: any, type?: any, pixels?: any) { const length = arguments.length; if (length === 9) { - if (pixels && typeof pixels.tagName === 'string' && (pixels.tagName === 'VID' || pixels.tagName === 'VIDEO') && pixels._video && typeof pixels._video.getCurrentFrame === 'function') { + pixels = fromSvgSource(pixels); + if (NAPI_HOST && isTexImageSource(pixels)) { + // As in texImage2D: sources go through the unsized form on Node-API hosts. + this.texSubImage2D(target, level, xoffset, yoffset, format, type, pixels); + } else if (pixels && typeof pixels.tagName === 'string' && (pixels.tagName === 'VID' || pixels.tagName === 'VIDEO') && pixels._video && typeof pixels._video.getCurrentFrame === 'function') { pixels._video.getCurrentFrame(this.native, this, target, level, format, format); } else if (pixels && typeof pixels.getCurrentFrame === 'function') { pixels.getCurrentFrame(this.native, this, target, level, format, format); @@ -1039,7 +1052,7 @@ export class WebGLRenderingContextBase extends WebGLRenderingCommon { } } else if (format instanceof ImageAsset) { this.native.texSubImage2D(target, level, xoffset, yoffset, width, height, format.native); - } else if ((isCanvasView(format) || isOffscreenCanvas(format))) { + } else if (isCanvasView(format) || isOffscreenCanvas(format)) { this.native.texSubImage2D(target, level, xoffset, yoffset, width, height, format.native); } else if (format instanceof ImageBitmap) { this.native.texSubImage2D(target, level, xoffset, yoffset, width, height, format.native);