From 4f9870569025ee90862ea7018e763469b4f7af84 Mon Sep 17 00:00:00 2001 From: Rob Bateman Date: Sun, 15 Sep 2024 01:32:05 +0100 Subject: [PATCH 1/4] use GPU fillRect() by default and use GPU setPixel32() --- lib/image/BitmapImage2D.ts | 208 ++++++++++++++++++++++++++----------- lib/webgl/ContextWebGL.ts | 2 +- 2 files changed, 147 insertions(+), 63 deletions(-) diff --git a/lib/image/BitmapImage2D.ts b/lib/image/BitmapImage2D.ts index fbc9bebb..3aa43db1 100644 --- a/lib/image/BitmapImage2D.ts +++ b/lib/image/BitmapImage2D.ts @@ -128,6 +128,7 @@ export class BitmapImage2D extends Image2D implements IUnloadable { return this._unloadManager; } + private _asyncRead: Promise; public _lazySymbol: LazyImageSymbolTag; @@ -145,9 +146,76 @@ export class BitmapImage2D extends Image2D implements IUnloadable { protected _nestedBitmap: BitmapImage2D[] = []; protected _sourceBitmap: BitmapImage2D; - // request a _data field without a calling getter of 'data' + protected _imageDataDirty: boolean; + + protected _initalFillColor: number = null; + protected _lastUsedFill: number = null; + + protected syncData(async = false): boolean | Promise { + + if (async && this._asyncRead) { + return this._asyncRead; + } + + if (!async && this._asyncRead) { + throw '[SceneImage2D] Synced read not allowed while async read is requested!'; + } + + this.applySymbol(); + + // update data from pixels from GPU + if (!this._imageDataDirty) { + return async ? Promise.resolve(false) : false; + } + + const context = this._stage.context; + + this._stage.setRenderTarget(this, false); + + // when we call syncData, we already loose other data + // not require apply symbol etc, because it already should be applied + if (!this._data) { + this._data = new Uint8ClampedArray(this.width * this.height * 4); + } + + // mark that this internal call, avoid reqursion loop + this._asyncRead = context.drawToBitmapImage2D(this, false, async); + + this._stage.setRenderTarget(null); + + if (!async) { + this._imageDataDirty = false; + // we store pixel buffer already as PMA. + // we should prevent unpack what already is PMA + this._unpackPMA = false; + return true; + } + + return this._asyncRead.then((_status: boolean) => { + this._imageDataDirty = false; + this._unpackPMA = false; + this._asyncRead = null; + + return true; + }); + } + + public getDataInternal(constructEmpty = true, skipSync = false): Uint8ClampedArray { + + // if it empty, fill with initlal value + if (this._initalFillColor !== null) { + //use CPU fill to avoid a readback when syncing + this.fillRect(this.rect, this._initalFillColor, !skipSync); + this._initalFillColor = null; + } + + if (!skipSync && this._imageDataDirty) { + // sync data already should fill _data + this.syncData(false); + + return this._data; + } - /*internal*/ getDataInternal(constructEmpty = true, skipSync = false): Uint8ClampedArray { this.applySymbol(); if (!this._data && (constructEmpty || this._alphaChannel)) { @@ -333,13 +401,14 @@ export class BitmapImage2D extends Image2D implements IUnloadable { this._transparent = transparent; this._stage = stage; - if (fillColor != null) - this.fillRect(this._rect, fillColor); + this._initalFillColor = fillColor; + this._lastUsedFill = fillColor; } public addLazySymbol(tag: LazyImageSymbolTag) { this._lazySymbol = tag; this._isSymbolSource = true; + this._initalFillColor = null; this.invalidateGPU(); } @@ -1066,59 +1135,82 @@ export class BitmapImage2D extends Image2D implements IUnloadable { * 0xFF336699. * @throws TypeError The rect is null. */ - public fillRect(rect: Rectangle, color: number): void { + public fillRect(rect: Rectangle, color: number, useCPU: boolean = false): void { this.dropAllReferences(); - if (!this._data) { - try { - this._data = new Uint8ClampedArray(this.width * this.height * 4); - } catch (e) { - console.error(this.width, this.height); + if (useCPU) { + if (!this._data) { + try { + this._data = new Uint8ClampedArray(this.width * this.height * 4); + } catch (e) { + console.error(this.width, this.height); + } } - } - const - x = ~~rect.x, - y = ~~rect.y, - width = ~~rect.width, - height = ~~rect.height, - data = new Uint32Array(this._data.buffer); - - let rgba = 0; - if (this._transparent) { - const [a, r, g, b] = ColorUtils.float32ColorToARGB(color); - // PMA - // we should FLIP bytes because a use UINT32 - rgba = ColorUtils.ARGBtoFloat32( - a, - b * a / 0xff | 0, - g * a / 0xff | 0, - r * a / 0xff | 0) >>> 0; - - /** - * TW2 has bug with transition over timeline when used a PMA - * I think that caused by invalid blend mode - */ - this._unpackPMA = false; - } else { - rgba = fastARGB_to_ABGR(color & 0xffffff, false); - } + const + x = ~~rect.x, + y = ~~rect.y, + width = ~~rect.width, + height = ~~rect.height, + data = new Uint32Array(this._data.buffer); + + let rgba = 0; + if (this._transparent) { + const [a, r, g, b] = ColorUtils.float32ColorToARGB(color); + // PMA + // we should FLIP bytes because a use UINT32 + rgba = ColorUtils.ARGBtoFloat32( + a, + b * a / 0xff | 0, + g * a / 0xff | 0, + r * a / 0xff | 0) >>> 0; + + /** + * TW2 has bug with transition over timeline when used a PMA + * I think that caused by invalid blend mode + */ + this._unpackPMA = false; + } else { + rgba = fastARGB_to_ABGR(color & 0xffffff, false); + } - //fast path for complete fill - if (x == 0 && y == 0 && width == this._rect.width && height == this._rect.height) { - data.fill(rgba); - } else { - let j: number; - let index: number; - for (j = 0; j < height; ++j) { + //fast path for complete fill + if (x == 0 && y == 0 && width == this._rect.width && height == this._rect.height) { + data.fill(rgba); + } else { + let j: number; + let index: number; + for (j = 0; j < height; ++j) { - index = x + (j + y) * this._rect.width; + index = x + (j + y) * this._rect.width; - data.fill(rgba, index, index + width); + data.fill(rgba, index, index + width); + } } - } - this.invalidateGPU(); + this.invalidateGPU(); + } else { + const argb = ColorUtils.float32ColorToARGB(color); + const alpha = this._transparent ? argb[0] / 255 : 1; + const isCrop = rect !== this._rect && !this._rect.equals(rect); + + this._stage.setRenderTarget(this, true, 0, 0, true); + this._stage.setScissor(rect); + + // we shure that color is fully filled when there are not any crops + this._lastUsedFill = isCrop ? null : color; + + this._stage.clear( + (argb[1] / 0xff) * alpha, + (argb[2] / 0xff) * alpha, + (argb[3] / 0xff) * alpha, + alpha + ); + + this._stage.setScissor(null); + + this._imageDataDirty = true; + } } /** @@ -1393,20 +1485,12 @@ export class BitmapImage2D extends Image2D implements IUnloadable { if (!this._rect.contains(x, y)) return; - this.dropAllReferences(); - - const index = (~~x + ~~y * this._rect.width) * 4; - const argb = ColorUtils.float32ColorToARGB(color); - const data = this.getDataInternal(true); - - const factor = this._transparent ? argb[0] / 0xff : 1; - - data[index + 0] = argb[1] * factor | 0; - data[index + 1] = argb[2] * factor | 0; - data[index + 2] = argb[3] * factor | 0; - data[index + 3] = this._transparent ? argb[0] : 0xFF; - - this.invalidateGPU(); + if (this._initalFillColor !== null) { + this.fillRect(this._rect, this._initalFillColor); + this._initalFillColor = null; + } + + this.fillRect(new Rectangle(x, y, 1, 1), color); } /** diff --git a/lib/webgl/ContextWebGL.ts b/lib/webgl/ContextWebGL.ts index 57245e53..51e240e9 100644 --- a/lib/webgl/ContextWebGL.ts +++ b/lib/webgl/ContextWebGL.ts @@ -322,7 +322,7 @@ export class ContextWebGL implements IContextGL { this.stateChangeCallback && this.stateChangeCallback('drawToBitmap'); - const pixels = new Uint8Array(destination.getDataInternal().buffer); + const pixels = new Uint8Array(destination.getDataInternal(true, true).buffer); const rt = this._texContext._currentRT; const fence = this._fenceContext; const { width, height } = destination; From 8bbd89927a137dafb6c51b7947439c51180acbcd Mon Sep 17 00:00:00 2001 From: Rob Bateman Date: Sun, 15 Sep 2024 01:34:38 +0100 Subject: [PATCH 2/4] fixed by eslint --- lib/image/BitmapImage2D.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/image/BitmapImage2D.ts b/lib/image/BitmapImage2D.ts index 3aa43db1..07158e87 100644 --- a/lib/image/BitmapImage2D.ts +++ b/lib/image/BitmapImage2D.ts @@ -128,6 +128,7 @@ export class BitmapImage2D extends Image2D implements IUnloadable { return this._unloadManager; } + private _asyncRead: Promise; public _lazySymbol: LazyImageSymbolTag; @@ -1489,7 +1490,7 @@ export class BitmapImage2D extends Image2D implements IUnloadable { this.fillRect(this._rect, this._initalFillColor); this._initalFillColor = null; } - + this.fillRect(new Rectangle(x, y, 1, 1), color); } From 780a485b93304a3d1aa963d889be91fd43197421 Mon Sep 17 00:00:00 2001 From: Rob Bateman Date: Sun, 15 Sep 2024 01:34:39 +0100 Subject: [PATCH 3/4] update version number in index.ts --- index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.ts b/index.ts index f2ed3ced..899d71b4 100644 --- a/index.ts +++ b/index.ts @@ -1,4 +1,4 @@ -console.debug('AwayJS - Stage - 0.11.147'); +console.debug('AwayJS - Stage - 0.11.148'); export { AGALMiniAssembler } from './lib/aglsl/assembler/AGALMiniAssembler'; export { Flags } from './lib/aglsl/assembler/Flags'; From 12443f2fddb704e1366e241f17f9e62e311c2d49 Mon Sep 17 00:00:00 2001 From: Rob Bateman Date: Sun, 15 Sep 2024 01:34:44 +0100 Subject: [PATCH 4/4] 0.11.148 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f2e58222..9b2ab03f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@awayjs/stage", - "version": "0.11.147", + "version": "0.11.148", "description": "Stage for AwayJS", "main": "bundle/awayjs-stage.umd.js", "module": "dist/index.js",