From b7462a1f1ab008a61a80da5781b682e72d846c4d Mon Sep 17 00:00:00 2001 From: Alexapp Date: Sat, 11 Oct 2025 18:28:40 +0300 Subject: [PATCH 1/3] Add signal for abort operation --- .../graphics/webgl/webgl-graphics-device.js | 63 ++++++++++++------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/src/platform/graphics/webgl/webgl-graphics-device.js b/src/platform/graphics/webgl/webgl-graphics-device.js index d85e199cc6d..b1bc9d1a688 100644 --- a/src/platform/graphics/webgl/webgl-graphics-device.js +++ b/src/platform/graphics/webgl/webgl-graphics-device.js @@ -2004,17 +2004,28 @@ class WebglGraphicsDevice extends GraphicsDevice { gl.readPixels(x, y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels); } - clientWaitAsync(flags, interval_ms) { + clientWaitAsync(flags, interval_ms, signal) { + const gl = this.gl; const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0); this.submit(); + let timeoutId; + return new Promise((resolve, reject) => { + + signal?.addEventListener("abort", () => { + gl?.deleteSync(sync); + clearTimeout(timeoutId); + timeoutId = undefined; + reject(new Error('Aborted by signal', { cause: signal.reason })); + }); + function test() { const res = gl.clientWaitSync(sync, flags, 0); if (res === gl.TIMEOUT_EXPIRED) { // check again in a while - setTimeout(test, interval_ms); + timeoutId = setTimeout(test, interval_ms); } else { gl.deleteSync(sync); if (res === gl.WAIT_FAILED) { @@ -2037,36 +2048,43 @@ class WebglGraphicsDevice extends GraphicsDevice { * @param {number} w - The width of the rectangle, in pixels. * @param {number} h - The height of the rectangle, in pixels. * @param {ArrayBufferView} pixels - The ArrayBufferView object that holds the returned pixel + * @param {AbortSignal} [signal] - The operation early interrupt signal * data. * @ignore */ - async readPixelsAsync(x, y, w, h, pixels) { + async readPixelsAsync(x, y, w, h, pixels, signal) { + const gl = this.gl; + const buf = gl.createBuffer(); - const impl = this.renderTarget.colorBuffer?.impl; - const format = impl?._glFormat ?? gl.RGBA; - const pixelType = impl?._glPixelType ?? gl.UNSIGNED_BYTE; + try { + const impl = this.renderTarget.colorBuffer?.impl; + const format = impl?._glFormat ?? gl.RGBA; + const pixelType = impl?._glPixelType ?? gl.UNSIGNED_BYTE; - // create temporary (gpu-side) buffer and copy data into it - const buf = gl.createBuffer(); - gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf); - gl.bufferData(gl.PIXEL_PACK_BUFFER, pixels.byteLength, gl.STREAM_READ); - gl.readPixels(x, y, w, h, format, pixelType, 0); - gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null); + // create temporary (gpu-side) buffer and copy data into it + + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf); + gl.bufferData(gl.PIXEL_PACK_BUFFER, pixels.byteLength, gl.STREAM_READ); + gl.readPixels(x, y, w, h, format, pixelType, 0); + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null); - // async wait for previous read to finish - await this.clientWaitAsync(0, 16); + // async wait for previous read to finish + await this.clientWaitAsync(0, 16, signal); - // copy the resulting data once it's arrived - gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf); - gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, pixels); - gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null); - gl.deleteBuffer(buf); + // copy the resulting data once it's arrived + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf); + gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, pixels); + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null); - return pixels; + return pixels; + } + finally { + gl.deleteBuffer(buf); + } } - readTextureAsync(texture, x, y, width, height, options) { + readTextureAsync(texture, x, y, width, height, options, signal) { const face = options.face ?? 0; @@ -2085,7 +2103,7 @@ class WebglGraphicsDevice extends GraphicsDevice { this.initRenderTarget(renderTarget); return new Promise((resolve, reject) => { - this.readPixelsAsync(x, y, width, height, data).then((data) => { + this.readPixelsAsync(x, y, width, height, data, signal).then((data) => { // return if the device was destroyed if (this._destroyed) return; @@ -2112,6 +2130,7 @@ class WebglGraphicsDevice extends GraphicsDevice { gl.bindTexture(gl.TEXTURE_2D, impl._glTexture); gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, width, height, format, pixelType, 0); gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null); + gl.deleteBuffer(buf); texture._needsUpload = false; texture._mipmapsUploaded = false; From b356d301ee28f1c9ece422838701c872ad5867eb Mon Sep 17 00:00:00 2001 From: Alexapp Date: Sat, 11 Oct 2025 18:41:51 +0300 Subject: [PATCH 2/3] Fixed readTextureAsync --- .../graphics/webgl/webgl-graphics-device.js | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/platform/graphics/webgl/webgl-graphics-device.js b/src/platform/graphics/webgl/webgl-graphics-device.js index b1bc9d1a688..7c0c319484f 100644 --- a/src/platform/graphics/webgl/webgl-graphics-device.js +++ b/src/platform/graphics/webgl/webgl-graphics-device.js @@ -2014,7 +2014,7 @@ class WebglGraphicsDevice extends GraphicsDevice { return new Promise((resolve, reject) => { - signal?.addEventListener("abort", () => { + signal?.addEventListener('abort', () => { gl?.deleteSync(sync); clearTimeout(timeoutId); timeoutId = undefined; @@ -2055,15 +2055,14 @@ class WebglGraphicsDevice extends GraphicsDevice { async readPixelsAsync(x, y, w, h, pixels, signal) { const gl = this.gl; - const buf = gl.createBuffer(); + const buf = gl.createBuffer(); // create temporary (gpu-side) buffer try { const impl = this.renderTarget.colorBuffer?.impl; const format = impl?._glFormat ?? gl.RGBA; const pixelType = impl?._glPixelType ?? gl.UNSIGNED_BYTE; - // create temporary (gpu-side) buffer and copy data into it - + // copy data into temporary (gpu-side) buffer gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf); gl.bufferData(gl.PIXEL_PACK_BUFFER, pixels.byteLength, gl.STREAM_READ); gl.readPixels(x, y, w, h, format, pixelType, 0); @@ -2078,8 +2077,8 @@ class WebglGraphicsDevice extends GraphicsDevice { gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null); return pixels; - } - finally { + + } finally { gl.deleteBuffer(buf); } } @@ -2094,15 +2093,21 @@ class WebglGraphicsDevice extends GraphicsDevice { depth: false, face: face }); + Debug.assert(renderTarget.colorBuffer === texture); - const buffer = new ArrayBuffer(TextureUtils.calcLevelGpuSize(width, height, 1, texture._format)); - const data = options.data ?? new (getPixelFormatArrayType(texture._format))(buffer); + let data = options.data; + + if (!data) { + const buffer = new ArrayBuffer(TextureUtils.calcLevelGpuSize(width, height, 1, texture._format)); + data = new (getPixelFormatArrayType(texture._format))(buffer); + } this.setRenderTarget(renderTarget); this.initRenderTarget(renderTarget); return new Promise((resolve, reject) => { + this.readPixelsAsync(x, y, width, height, data, signal).then((data) => { // return if the device was destroyed @@ -2112,8 +2117,10 @@ class WebglGraphicsDevice extends GraphicsDevice { if (!options.renderTarget) { renderTarget.destroy(); } + resolve(data); - }).catch(reject); + }) + .catch(reject); }); } From 0c111ec6c4237069870c1f804cce11dd077144d6 Mon Sep 17 00:00:00 2001 From: Alexapp Date: Sat, 11 Oct 2025 19:45:52 +0300 Subject: [PATCH 3/3] Refactoring --- src/platform/graphics/webgl/webgl-graphics-device.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/platform/graphics/webgl/webgl-graphics-device.js b/src/platform/graphics/webgl/webgl-graphics-device.js index 7c0c319484f..90c72ab2368 100644 --- a/src/platform/graphics/webgl/webgl-graphics-device.js +++ b/src/platform/graphics/webgl/webgl-graphics-device.js @@ -2014,12 +2014,13 @@ class WebglGraphicsDevice extends GraphicsDevice { return new Promise((resolve, reject) => { - signal?.addEventListener('abort', () => { - gl?.deleteSync(sync); + function handleAbort() { clearTimeout(timeoutId); timeoutId = undefined; + gl?.deleteSync(sync); + signal?.removeEventListener('abort', handleAbort); reject(new Error('Aborted by signal', { cause: signal.reason })); - }); + } function test() { const res = gl.clientWaitSync(sync, flags, 0); @@ -2035,6 +2036,8 @@ class WebglGraphicsDevice extends GraphicsDevice { } } } + + signal?.addEventListener('abort', handleAbort); test(); }); }