UNPKG

@awayjs/scene

Version:
609 lines (608 loc) 29 kB
import { __extends } from "tslib"; import { View } from '@awayjs/view'; import { MethodMaterial } from '@awayjs/materials'; import { DisplayObjectContainer } from '../display/DisplayObjectContainer'; import { DisplayObject } from '../display/DisplayObject'; import { Billboard } from '../display/Billboard'; import { Settings } from '../Settings'; import { Rectangle, Point, PerspectiveProjection, CoordinateSystem, Vector3D, Transform, ColorUtils, } from '@awayjs/core'; import { Stage, BitmapImage2D, _Stage_BitmapImage2D, BlendMode } from '@awayjs/stage'; import { DefaultRenderer, RenderGroup, Style } from '@awayjs/renderer'; // empty matrix for transform reset var TMP_POINT = new Point(0, 0); /** * */ var SceneImage2D = /** @class */ (function (_super) { __extends(SceneImage2D, _super); /** * Creates a BitmapImage2D object with a specified width and height. If you * specify a value for the <code>fillColor</code> parameter, every pixel in * the bitmap is set to that color. * * <p>By default, the bitmap is created as transparent, unless you pass * the value <code>false</code> for the transparent parameter. After you * create an opaque bitmap, you cannot change it to a transparent bitmap. * Every pixel in an opaque bitmap uses only 24 bits of color channel * information. If you define the bitmap as transparent, every pixel uses 32 * bits of color channel information, including an alpha transparency * channel.</p> * * @param width The width of the bitmap image in pixels. * @param height The height of the bitmap image in pixels. * @param transparent Specifies whether the bitmap image supports per-pixel * transparency. The default value is <code>true</code> * (transparent). To create a fully transparent bitmap, * set the value of the <code>transparent</code> * parameter to <code>true</code> and the value of the * <code>fillColor</code> parameter to 0x00000000(or to * 0). Setting the <code>transparent</code> property to * <code>false</code> can result in minor improvements * in rendering performance. * @param fillColor A 32-bit ARGB color value that you use to fill the * bitmap image area. The default value is * 0xFFFFFFFF(solid white). */ function SceneImage2D(width, height, transparent, fillColor, powerOfTwo, stage) { if (transparent === void 0) { transparent = true; } if (fillColor === void 0) { fillColor = 0xffffffff; } if (powerOfTwo === void 0) { powerOfTwo = true; } if (stage === void 0) { stage = null; } var _this = _super.call(this, width, height, transparent, fillColor, powerOfTwo, stage) || this; _this._msaaNeedDrop = false; _this._enforceMSAASupport = false; /*private*/ _this._antialiasQuality = Settings.ALLOW_FORCE_MSAA; _this._clearFromDispose = false; return _this; } SceneImage2D.getImage = function (width, height, transparent, fillColor, powerOfTwo, stage, msaa) { if (transparent === void 0) { transparent = true; } if (fillColor === void 0) { fillColor = 0xffffffff; } if (powerOfTwo === void 0) { powerOfTwo = true; } if (stage === void 0) { stage = null; } if (msaa === void 0) { msaa = false; } var result = new SceneImage2D(width, height, transparent, fillColor, powerOfTwo, stage); if (!msaa) { result._msaaNeedDrop = true; result._antialiasQuality = 0; } else { result._enforceMSAASupport = true; } return result; }; SceneImage2D.tryStoreImage = function (image, stage) { stage.filterManager.pushTemp(image); }; SceneImage2D.getTemp = function (width, height, stage, msaa) { if (msaa === void 0) { msaa = false; } var image = stage.filterManager.popTemp(width, height, msaa && Settings.ALLOW_FORCE_MSAA > 1); image.antialiasQuality = msaa ? Settings.ALLOW_FORCE_MSAA : 0; return image; }; SceneImage2D.prototype._dropMSAA = function () { if (this._msaaNeedDrop || !this.canUseMSAAInternaly) { return; } this._enforceMSAASupport = false; this._msaaNeedDrop = true; // force dispose texture and buffers // MSAA not stored in pool if (this.wasUpload) { // super unload not call sync, we call it _super.prototype.unload.call(this); } console.debug('[SceneImage2D Experemental] Drop MSAA support because a setPixel* operation called after upload.', this.id); }; Object.defineProperty(SceneImage2D.prototype, "canUseMSAAInternaly", { get: function () { if (this._enforceMSAASupport) { return true; } var minW = Settings.MSAA_MINIMAL_IMAGE_SIZE; var minH = Settings.MSAA_MINIMAL_IMAGE_SIZE; if (this._stage) { minH = Math.min(this._stage.height, minH); minW = Math.min(this._stage.width, minW); } return (this.width >= minW && this.height >= minH && !this._msaaNeedDrop); }, enumerable: false, configurable: true }); Object.defineProperty(SceneImage2D.prototype, "antialiasQuality", { get: function () { return this.canUseMSAAInternaly ? this._antialiasQuality : 0; }, enumerable: false, configurable: true }); Object.defineProperty(SceneImage2D.prototype, "assetType", { /** * * @returns {string} */ get: function () { return SceneImage2D.assetType; }, enumerable: false, configurable: true }); SceneImage2D.prototype.createRenderer = function () { //create the projection var projection = new PerspectiveProjection(); projection.coordinateSystem = CoordinateSystem.RIGHT_HANDED; projection.originX = -1; projection.originY = 1; projection.transform = new Transform(); projection.transform.scaleTo(1, -1, 1); projection.transform.moveTo(0, 0, -1000); //create the view SceneImage2D._view = new View(projection, this._stage); SceneImage2D._root = new DisplayObjectContainer(); SceneImage2D._rootNode = SceneImage2D._view.getNode(SceneImage2D._root); SceneImage2D._renderer = RenderGroup .getInstance(DefaultRenderer) .getRenderer(SceneImage2D._rootNode); //set the view properties SceneImage2D._view.backgroundAlpha = 0; SceneImage2D._view.backgroundColor = 0x0; //set the renderer properties SceneImage2D._renderer.disableClear = true; SceneImage2D._renderer.renderableSorter = null; //new RenderableSort2D(); //SceneImage2D._renderer.antiAlias = Settings.ALLOW_FORCE_MSAA; }; SceneImage2D.prototype.createBillboardRenderer = function () { //create the projection var projection = new PerspectiveProjection(); projection.coordinateSystem = CoordinateSystem.RIGHT_HANDED; projection.originX = -1; projection.originY = 1; projection.transform = new Transform(); projection.transform.moveTo(0, 0, -1000); projection.transform.lookAt(new Vector3D()); //create the view SceneImage2D._billboardView = new View(projection, this._stage); SceneImage2D._billboardRoot = new DisplayObjectContainer(); SceneImage2D._billboardRenderer = RenderGroup .getInstance(DefaultRenderer) .getRenderer(SceneImage2D._billboardView.getNode(SceneImage2D._billboardRoot)); //SceneImage2D._billboardRoot.partition = SceneImage2D._billboardRenderer.partition; //SceneImage2D._renderer.antiAlias = Settings.ALLOW_FORCE_MSAA; //set the view properties SceneImage2D._billboardView.backgroundAlpha = 0; SceneImage2D._billboardView.backgroundColor = 0x0; //set the renderer properties SceneImage2D._billboardRenderer.disableClear = true; SceneImage2D._billboardRenderer.renderableSorter = null; //new RenderableSort2D(); var mat = new MethodMaterial(new BitmapImage2D(128, 128, true, 0x0)); mat.bothSides = true; mat.alphaBlending = true; SceneImage2D._billboard = new Billboard(mat); SceneImage2D._billboard.style = new Style(); SceneImage2D._billboardRoot.addChild(SceneImage2D._billboard); }; /** * Frees memory that is used to store the BitmapImage2D object. * * <p>When the <code>dispose()</code> method is called on an image, the width * and height of the image are set to 0. All subsequent calls to methods or * properties of this BitmapImage2D instance fail, and an exception is thrown. * </p> * * <p><code>BitmapImage2D.dispose()</code> releases the memory occupied by the * actual bitmap data, immediately(a bitmap can consume up to 64 MB of * memory). After using <code>BitmapImage2D.dispose()</code>, the BitmapImage2D * object is no longer usable and an exception may be thrown if * you call functions on the BitmapImage2D object. However, * <code>BitmapImage2D.dispose()</code> does not garbage collect the BitmapImage2D * object(approximately 128 bytes); the memory occupied by the actual * BitmapImage2D object is released at the time the BitmapImage2D object is * collected by the garbage collector.</p> * */ SceneImage2D.prototype.dispose = function () { this._clearFromDispose = true; this.dropAllReferences(); this.unmarkToUnload(); this.unuseWeakRef(); // drop buffer, because is big this._data = null; this._locked = false; _super.prototype.dispose.call(this); this._clearFromDispose = false; }; SceneImage2D.prototype.unload = function () { var _this = this; // query async unload if (this._imageDataDirty) { var t = this.syncData(true); // strict quard if (typeof t !== 'boolean') { t.then(function () { return _super.prototype.unload.call(_this); }); return; } } _super.prototype.unload.call(this); }; SceneImage2D.prototype.deepClone = function (from) { this.copyPixels(from, this._rect, new Point(0, 0)); }; /** * Provides a fast routine to perform pixel manipulation between images with * no stretching, rotation, or color effects. This method copies a * rectangular area of a source image to a rectangular area of the same size * at the destination point of the destination BitmapImage2D object. * * <p>If you include the <code>alphaBitmap</code> and <code>alphaPoint</code> * parameters, you can use a secondary image as an alpha source for the * source image. If the source image has alpha data, both sets of alpha data * are used to composite pixels from the source image to the destination * image. The <code>alphaPoint</code> parameter is the point in the alpha * image that corresponds to the upper-left corner of the source rectangle. * Any pixels outside the intersection of the source image and alpha image * are not copied to the destination image.</p> * * <p>The <code>mergeAlpha</code> property controls whether or not the alpha * channel is used when a transparent image is copied onto another * transparent image. To copy pixels with the alpha channel data, set the * <code>mergeAlpha</code> property to <code>true</code>. By default, the * <code>mergeAlpha</code> property is <code>false</code>.</p> * * @param source The input bitmap image from which to copy pixels. * The source image can be a different BitmapImage2D * instance, or it can refer to the current * BitmapImage2D instance. * @param sourceRect A rectangle that defines the area of the source * image to use as input. * @param destPoint The destination point that represents the * upper-left corner of the rectangular area where * the new pixels are placed. * @param alphaBitmapData A secondary, alpha BitmapImage2D object source. * @param alphaPoint The point in the alpha BitmapImage2D object source * that corresponds to the upper-left corner of the * <code>sourceRect</code> parameter. * @param mergeAlpha To use the alpha channel, set the value to * <code>true</code>. To copy pixels with no alpha * channel, set the value to <code>false</code>. * @throws TypeError The sourceBitmapImage2D, sourceRect, destPoint are null. */ SceneImage2D.prototype.copyPixels = function (source, sourceRect, destPoint, alphaBitmapData, alphaPoint, mergeAlpha) { this._lastUsedFill = null; this.dropAllReferences(); this.unmarkToUnload(); // need drop alpha from source when target is not has alpha mergeAlpha = this.transparent !== source.transparent || mergeAlpha; // CPU based copy, not require run GPU based copy // block is equal, one of image not uploaded yet // and source image no require SYNC if (!source._imageDataDirty && sourceRect.equals(this._rect) && this._rect.equals(source.rect) && !mergeAlpha && (!this.wasUpload || !source.wasUpload)) { var data = source.getDataInternal(true); // inline, instead of setPixels, because it use a lot of checks if (this._data) { this._data.set(data); } else { this._data = data.slice(); } // we should sync this, because we can apply PMA twice, that not needed for us this._unpackPMA = source._unpackPMA; // we should reset initial color, because not require fill it after copyPixel this._initalFillColor = null; this._imageDataDirty = false; this.invalidateGPU(); return; } if (source.width * source.height <= Settings.CPU_COPY_PIXELS_COUNT && !mergeAlpha) { // todo Not implemented yet, need to implement and check performance change // i think that increase some small operation performance, // because copy by GPU required upload array to VRAM, // and games that use set/get pixels and copyPixels only for math process not required use GPU copy } if (this._initalFillColor !== null) this.fillRect(this._rect, this._initalFillColor); var compositeSource = source; // merge alpha with source if (alphaBitmapData) { compositeSource = this._stage.filterManager.popTemp(source.width, source.height); //this._stage.filterManager.copyPixels(source, compositeSource, source.rect, source.rect.topLeft); this._stage.filterManager.copyPixels(alphaBitmapData, compositeSource, new Rectangle(alphaPoint.x, alphaPoint.y, sourceRect.width, sourceRect.height), new Point(0, 0)); this._stage.filterManager.copyPixels(source, compositeSource, source.rect, new Point(0, 0), true, 'alpha_back'); } this._stage.filterManager.copyPixels(compositeSource, this, sourceRect, destPoint, mergeAlpha); if (alphaBitmapData) { this._stage.filterManager.pushTemp(compositeSource); } this._imageDataDirty = true; }; SceneImage2D.prototype.threshold = function (source, sourceRect, destPoint, operation, threshold, color, mask, copySource) { this._lastUsedFill = null; this.dropAllReferences(); this.unmarkToUnload(); if (this._initalFillColor !== null) this.fillRect(this._rect, this._initalFillColor); this._stage.threshold(source, this, sourceRect, destPoint, operation, threshold, color, mask, copySource); this._imageDataDirty = true; }; SceneImage2D.prototype.applyFilter = function (source, sourceRect, destPoint, filter) { if (!Settings.USE_UNSAFE_FILTERS || !filter || !filter.filterName) { return false; } this.dropAllReferences(false); var result = this._stage.filterManager.applyFilter(source, this, sourceRect, destPoint, filter.filterName, filter); this._imageDataDirty = result; return result; }; SceneImage2D.prototype.colorTransform = function (rect, colorTransform) { this.dropAllReferences(); this.unmarkToUnload(); this._lastUsedFill = null; this._stage.colorTransform(this, this, rect, colorTransform); this._imageDataDirty = true; }; SceneImage2D.prototype.setPixel = function (x, y, color) { // we can't upload buffer in MSAA texture after creating - only render it and get if (this.canUseMSAAInternaly) { this._dropMSAA(); } _super.prototype.setPixel.call(this, x, y, color); }; SceneImage2D.prototype.setPixel32 = function (x, y, color) { // we can't upload buffer in MSAA texture after creating - only render it and get if (this.canUseMSAAInternaly) { this._dropMSAA(); } _super.prototype.setPixel32.call(this, x, y, color); }; SceneImage2D.prototype.setPixels = function (rect, buffer) { // we can't upload buffer in MSAA texture after creating - only render it and get if (this.wasUpload && this.canUseMSAAInternaly) { this._dropMSAA(); } _super.prototype.setPixels.call(this, rect, buffer); }; SceneImage2D.prototype.clear = function () { var _this = this; // we call clear in parent class from dispose, call direct if (this._clearFromDispose) { _super.prototype.clear.call(this); return; } // clear should drop abstraction, but we can't doing this if unloaded var t = this.syncData(true); // not require unload, we already doings this this.unmarkToUnload(); this.lastUsedTime = -1; if (typeof t === 'boolean') { this.wasUpload = false; _super.prototype.clear.call(this); return; } t.then(function () { _this.wasUpload = false; _super.prototype.clear.call(_this); }); }; /** * @inheritdoc */ SceneImage2D.prototype.getPixel32 = function (x, y) { this.syncData(); return _super.prototype.getPixel32.call(this, x, y); }; /** * @inheritdoc */ SceneImage2D.prototype.getPixel = function (x, y) { var result = this.getPixel32(x, y); return result & 0x00ffffff; }; SceneImage2D.prototype.draw = function (source, matrix, colorTransform, blendMode, clipRect, smoothing) { /* eslint-enable */ this.dropAllReferences(); this.unmarkToUnload(); if (source instanceof DisplayObject) { this._drawAsDisplay(source, matrix, colorTransform, blendMode, clipRect, smoothing); } else { this._drawAsBitmap(source, matrix, colorTransform, blendMode, clipRect, smoothing); } this._lastUsedFill = null; this._imageDataDirty = true; this.invalidate(); }; SceneImage2D._mapSupportedBlendMode = function (blendMode) { if (blendMode === void 0) { blendMode = ''; } switch (blendMode) { case null: case '': case BlendMode.NORMAL: case BlendMode.LAYER: return BlendMode.LAYER; case BlendMode.MULTIPLY: case BlendMode.ADD: case BlendMode.ALPHA: return blendMode; } //console.debug("[ImageBitmap] Unsupport BlendMode", blendMode); return BlendMode.LAYER; }; SceneImage2D.prototype._drawAsBitmap = function (source, matrix, colorTransform, blendMode, _clipRect, smoothing) { if (!SceneImage2D._billboardRenderer) { this.createBillboardRenderer(); } if (this._initalFillColor !== null) this.fillRect(this._rect, this._initalFillColor); var stage = this._stage; var mappedBlend = SceneImage2D._mapSupportedBlendMode(blendMode); var supportNativeBlend = !blendMode || mappedBlend !== BlendMode.LAYER || blendMode == BlendMode.LAYER; var useTmp = (!supportNativeBlend || this === source); var target = useTmp ? stage.filterManager.popTemp(this.width, this.height, false) : this; var renderer = SceneImage2D._billboardRenderer; var root = SceneImage2D._billboardRoot; var billboard = SceneImage2D._billboard; billboard.sampler.smooth = smoothing; renderer.disableClear = !useTmp; renderer.view.target = target; renderer.view.projection.scale = 1000 / target.height; billboard.material.style.image = source; // not all blend modes can be used for rendering billboard.material.blendMode = !useTmp ? SceneImage2D._mapSupportedBlendMode(blendMode) : BlendMode.LAYER; billboard.material.useColorTransform = !!colorTransform; if (matrix) { var m = root.transform.matrix3D; m.identity(); m._rawData[0] = matrix.a; m._rawData[1] = -matrix.b; m._rawData[4] = matrix.c; m._rawData[5] = -matrix.d; m._rawData[12] = matrix.tx; m._rawData[13] = target.height - matrix.ty; root.transform.invalidateComponents(); } else { root.transform.rotateTo(0, 0, 0); root.transform.scaleTo(1, -1, 1); root.transform.moveTo(0, target.height, 0); } root.transform.colorTransform = colorTransform; //render renderer.render(); // if (useTmp) { stage.filterManager.copyPixels(target, this, this.rect, TMP_POINT, true, blendMode); stage.filterManager.pushTemp(target); } }; SceneImage2D.prototype._drawAsDisplay = function (source, matrix, colorTransform, blendMode, _clipRect, _smoothing) { if (blendMode === void 0) { blendMode = ''; } // default global blend mode blendMode = blendMode || BlendMode.LAYER; if (!SceneImage2D._renderer) { this.createRenderer(); } var root = SceneImage2D._root; var rootNode = SceneImage2D._rootNode; var renderer = SceneImage2D._renderer; // when we should use MSAA, we will create temporary image and draw to it var internal = this.canUseMSAAInternaly; var nativeMSAA = (!source.isAsset(Billboard) && this._stage.context.glVersion === 2 && // can be used because a webgl2 Settings.ALLOW_FORCE_MSAA > 1 && // because a quality is more that 1 !internal); // and not internal // target image for rendering. var target = this; // we should run compositor when blendMode !== LAYER (default) // or when we use MSAA + fill is not flat. var useBlend = blendMode !== BlendMode.LAYER || this._lastUsedFill === null; var useTemp = useBlend || nativeMSAA; // lazy filling // we require fill image with initial color, because we not doing this immediate // and when blend is required, because we should blend with vald color if ((!nativeMSAA || useBlend) && this._initalFillColor !== null) this.fillRect(this._rect, this._initalFillColor); else this._initalFillColor = null; if (useTemp) { target = SceneImage2D.getTemp(this.width, this.height, this._stage, nativeMSAA); // because target image is stupid filled - it not require merging // BUT when blend is required - we shpuld skip color clear and use empty TMP if (!useBlend && this._lastUsedFill !== null) { if (this._transparent) { //premulitply fill color! var _a = ColorUtils.float32ColorToARGB(this._lastUsedFill), a = _a[0], r = _a[1], g = _a[2], b = _a[3]; a /= 0xFF; this._lastUsedFill = ColorUtils.ARGBtoFloat32(a * 0xFF, r * a | 0, g * a | 0, b * a | 0); } // bitmap was filled plain, go clear TMP to this color too renderer.disableClear = false; renderer.view.backgroundColor = this._lastUsedFill & 0xffffff; renderer.view.backgroundAlpha = this._transparent ? (this._lastUsedFill >>> 24 & 0xff) / 0xff : 1; } else { // we clear TMP and render to it, prepare to composing renderer.disableClear = false; renderer.view.backgroundColor = 0x0; renderer.view.backgroundAlpha = 0; // copy from source to tmp // TMP_POINT.setTo(0,0); // this._stage.copyPixels(this, target, this._rect, TMP_POINT, null, null, false); } } var transform = renderer.view.projection.transform; var mat3d = transform.matrix3D; mat3d.identity(); if (matrix) { var raw = mat3d._rawData; raw[0] = matrix.a; raw[1] = matrix.b; raw[4] = matrix.c; raw[5] = matrix.d; raw[10] = 1; raw[12] = matrix.tx; raw[13] = matrix.ty; } // todo By this line we flip normals, and cull will be broken // NEED FIX THIS ASAP, or flip cull state mat3d.appendScale(1, -1, 1); mat3d.appendTranslation(0, this._rect.height, 1000); mat3d.invert(); transform.matrix3D = mat3d; renderer.antiAlias = (internal ? this.antialiasQuality : target.antialiasQuality) || 0; renderer.view.target = target; renderer.view.projection.scale = 1000 / this._rect.height; // shift view, because target can be more renderer.view.projection.ratio = (this._rect.width / this._rect.height); renderer.view.x = -(target.width - this.width); renderer.view.y = -(target.height - this.height); renderer.view.width = this.width; renderer.view.height = this.height; var sourceNode = rootNode.getChildAt(0); if ((sourceNode === null || sourceNode === void 0 ? void 0 : sourceNode.container) != source) { if (sourceNode) rootNode.removeChildAt(0); sourceNode = rootNode.addChildAt(source, 0); } var transformDisabled = sourceNode.transformDisabled; sourceNode.transformDisabled = true; // color transform should be enabled! sourceNode.colorTransformDisabled = false; // masks should be enabled! sourceNode.maskDisabled = false; root.transform.colorTransform = colorTransform; // anyway we not support this =)) root.blendMode = SceneImage2D._mapSupportedBlendMode(blendMode); //save snapshot if unlocked //if (!this._locked) //SceneImage2D.scene.view.target=this; //SceneImage2D.scene.renderer.disableClear = !this._locked; renderer.render(); // reset render to default value renderer.antiAlias = 0; renderer.disableClear = true; sourceNode.transformDisabled = transformDisabled; if (useTemp) { // because we copy MSAA into no msaa, it should passed as BLIT this._stage.filterManager.copyPixels(target, this, this._rect, TMP_POINT, useBlend, blendMode // apply blend mode if this needed. ); SceneImage2D.tryStoreImage(target, this._stage); } }; SceneImage2D.prototype.reset = function () { }; SceneImage2D.prototype.clone = function () { var image = SceneImage2D.getImage(this.width, this.height, this.transparent, null, false, this._stage, false); image.deepClone(this); return image; }; SceneImage2D.assetType = '[image SceneImage2D]'; return SceneImage2D; }(BitmapImage2D)); export { SceneImage2D }; Stage.registerAbstraction(_Stage_BitmapImage2D, SceneImage2D);