Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 54 additions & 25 deletions src/platform/graphics/webgl/webgl-graphics-device.js
Original file line number Diff line number Diff line change
Expand Up @@ -2004,17 +2004,29 @@ 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) => {

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);
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) {
Expand All @@ -2024,6 +2036,8 @@ class WebglGraphicsDevice extends GraphicsDevice {
}
}
}

signal?.addEventListener('abort', handleAbort);
test();
});
}
Expand All @@ -2037,36 +2051,42 @@ 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(); // create temporary (gpu-side) buffer

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);
// 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);
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;

Expand All @@ -2076,16 +2096,22 @@ 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).then((data) => {

this.readPixelsAsync(x, y, width, height, data, signal).then((data) => {

// return if the device was destroyed
if (this._destroyed) return;
Expand All @@ -2094,8 +2120,10 @@ class WebglGraphicsDevice extends GraphicsDevice {
if (!options.renderTarget) {
renderTarget.destroy();
}

resolve(data);
}).catch(reject);
})
.catch(reject);
});
}

Expand All @@ -2112,6 +2140,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;
Expand Down