/
githubmirror
/
pixijs
Обзор
Документация
Войти
/
githubmirror
/
pixijs
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
src/scene/container/RenderGroup.ts
506 строк
14 KB
Ilia Malanin
fix: Advanced blending modes within render groups, cached as texture render groups (#11607)
22 авг 2025, 18:08
Не верифицирован
22 авг 2025, 18:08
55c3379
Код
Авторство
О чём код?
import { Matrix } from '../../maths/matrix/Matrix'; import { InstructionSet } from '../../rendering/renderers/shared/instructions/InstructionSet'; import { type SCALE_MODE } from '../../rendering/renderers/shared/texture/const'; import { TexturePool } from '../../rendering/renderers/shared/texture/TexturePool'; import { type Renderer } from '../../rendering/renderers/types'; import type { Instruction } from '../../rendering/renderers/shared/instructions/Instruction'; import type { Texture } from '../../rendering/renderers/shared/texture/Texture'; import type { BatchableSprite } from '../sprite/BatchableSprite'; import type { ViewContainer } from '../view/ViewContainer'; import type { Bounds } from './bounds/Bounds'; import type { Container } from './Container'; /** * Options for caching a container as a texture. * @category rendering * @advanced */ export interface CacheAsTextureOptions { /** * If true, the texture will be antialiased. This smooths out the edges of the texture. * @default false */ antialias?: boolean; /** * The resolution of the texture. A higher resolution means a sharper texture but uses more memory. * By default the resolution is 1 which is the same as the rendererers resolution. */ resolution?: number; /** * Scale Mode to use for the cached texture * @type {SCALE_MODE} * @default 'linear' * @example * ```ts * const container = new Container(); * container.cacheAsTexture({ scaleMode: 'nearest' }); * ``` * @see {@link SCALE_MODE} */ scaleMode?: SCALE_MODE; } /** * A RenderGroup is a class that is responsible for I generating a set of instructions that are used to render the * root container and its children. It also watches for any changes in that container or its children, * these changes are analysed and either the instruction set is rebuild or the instructions data is updated. * @category rendering * @advanced */ export class RenderGroup implements Instruction { public renderPipeId = 'renderGroup'; public root: Container = null; public canBundle = false; public renderGroupParent: RenderGroup = null; public renderGroupChildren: RenderGroup[] = []; public worldTransform: Matrix = new Matrix(); public worldColorAlpha = 0xffffffff; public worldColor = 0xffffff; public worldAlpha = 1; // these updates are transform changes.. public readonly childrenToUpdate: Record<number, { list: Container[]; index: number; }> = Object.create(null); public updateTick = 0; public gcTick = 0; // these update are renderable changes.. public readonly childrenRenderablesToUpdate: { list: Container[]; index: number; } = { list: [], index: 0 }; // other public structureDidChange = true; public instructionSet: InstructionSet = new InstructionSet(); private readonly _onRenderContainers: Container[] = []; /** * Indicates if the cached texture needs to be updated. * @default true */ public textureNeedsUpdate = true; /** * Indicates if the container should be cached as a texture. * @default false */ public isCachedAsTexture = false; /** * The texture used for caching the container. this is only set if isCachedAsTexture is true. * It can only be accessed after a render pass. * @type {Texture | undefined} */ public texture?: Texture; /** * The bounds of the cached texture. * @type {Bounds | undefined} * @ignore */ public _textureBounds?: Bounds; /** * The options for caching the container as a texture. * @type {CacheAsTextureOptions} */ public textureOptions: CacheAsTextureOptions; /** * holds a reference to the batchable render sprite * @ignore */ public _batchableRenderGroup: BatchableSprite; /** * Holds a reference to the closest parent RenderGroup that has isCachedAsTexture enabled. * This is used to properly transform coordinates when rendering into cached textures. * @type {RenderGroup | null} * @ignore */ public _parentCacheAsTextureRenderGroup: RenderGroup; private _inverseWorldTransform: Matrix; private _textureOffsetInverseTransform: Matrix; private _inverseParentTextureTransform: Matrix; private _matrixDirty = 0b111; public init(root: Container) { this.root = root; if (root._onRender) this.addOnRender(root); root.didChange = true; const children = root.children; for (let i = 0; i < children.length; i++) { const child = children[i]; // make sure the children are all updated on the first pass.. child._updateFlags = 0b1111; this.addChild(child); } } public enableCacheAsTexture(options: CacheAsTextureOptions = {}): void { this.textureOptions = options; this.isCachedAsTexture = true; this.textureNeedsUpdate = true; } public disableCacheAsTexture(): void { this.isCachedAsTexture = false; if (this.texture) { TexturePool.returnTexture(this.texture, true); this.texture = null; } } public updateCacheTexture(): void { this.textureNeedsUpdate = true; const cachedParent = this._parentCacheAsTextureRenderGroup; // It's worth going bottom-up and notify all parents cached as texture // that cached child was updated. if (cachedParent && !cachedParent.textureNeedsUpdate) { cachedParent.updateCacheTexture(); } } public reset() { this.renderGroupChildren.length = 0; for (const i in this.childrenToUpdate) { const childrenAtDepth = this.childrenToUpdate[i]; childrenAtDepth.list.fill(null); childrenAtDepth.index = 0; } this.childrenRenderablesToUpdate.index = 0; this.childrenRenderablesToUpdate.list.fill(null); this.root = null; this.updateTick = 0; this.structureDidChange = true; this._onRenderContainers.length = 0; this.renderGroupParent = null; this.disableCacheAsTexture(); } get localTransform() { return this.root.localTransform; } public addRenderGroupChild(renderGroupChild: RenderGroup) { if (renderGroupChild.renderGroupParent) { renderGroupChild.renderGroupParent._removeRenderGroupChild(renderGroupChild); } renderGroupChild.renderGroupParent = this; this.renderGroupChildren.push(renderGroupChild); } private _removeRenderGroupChild(renderGroupChild: RenderGroup) { const index = this.renderGroupChildren.indexOf(renderGroupChild); if (index > -1) { this.renderGroupChildren.splice(index, 1); } renderGroupChild.renderGroupParent = null; } public addChild(child: Container) { this.structureDidChange = true; child.parentRenderGroup = this; child.updateTick = -1; if (child.parent === this.root) { child.relativeRenderGroupDepth = 1; } else { child.relativeRenderGroupDepth = child.parent.relativeRenderGroupDepth + 1; } child.didChange = true; this.onChildUpdate(child); if (child.renderGroup) { this.addRenderGroupChild(child.renderGroup); return; } if (child._onRender) this.addOnRender(child); const children = child.children; for (let i = 0; i < children.length; i++) { this.addChild(children[i]); } } public removeChild(child: Container) { // remove all the children... this.structureDidChange = true; if (child._onRender) { // Remove the child to the onRender list under the following conditions: // 1. If the child is not a render group. // 2. If the child is a render group root of this render group - which it can't be removed from in this case. if (!child.renderGroup) { this.removeOnRender(child); } } child.parentRenderGroup = null; if (child.renderGroup) { this._removeRenderGroupChild(child.renderGroup); return; } const children = child.children; for (let i = 0; i < children.length; i++) { this.removeChild(children[i]); } } public removeChildren(children: Container[]) { for (let i = 0; i < children.length; i++) { this.removeChild(children[i]); } } public onChildUpdate(child: Container) { let childrenToUpdate = this.childrenToUpdate[child.relativeRenderGroupDepth]; if (!childrenToUpdate) { childrenToUpdate = this.childrenToUpdate[child.relativeRenderGroupDepth] = { index: 0, list: [], }; } childrenToUpdate.list[childrenToUpdate.index++] = child; } public updateRenderable(renderable: ViewContainer) { if (renderable.globalDisplayStatus < 0b111) return; this.instructionSet.renderPipes[renderable.renderPipeId].updateRenderable(renderable); renderable.didViewUpdate = false; } public onChildViewUpdate(child: Container) { this.childrenRenderablesToUpdate.list[this.childrenRenderablesToUpdate.index++] = child; } get isRenderable(): boolean { return (this.root.localDisplayStatus === 0b111 && this.worldAlpha > 0); } /** * adding a container to the onRender list will make sure the user function * passed in to the user defined 'onRender` callBack * @param container - the container to add to the onRender list */ public addOnRender(container: Container) { this._onRenderContainers.push(container); } public removeOnRender(container: Container) { this._onRenderContainers.splice(this._onRenderContainers.indexOf(container), 1); } public runOnRender(renderer: Renderer) { for (let i = 0; i < this._onRenderContainers.length; i++) { this._onRenderContainers[i]._onRender(renderer); } } public destroy() { this.disableCacheAsTexture(); this.renderGroupParent = null; this.root = null; (this.childrenRenderablesToUpdate as any) = null; (this.childrenToUpdate as any) = null; (this.renderGroupChildren as any) = null; (this._onRenderContainers as any) = null; this.instructionSet = null; } public getChildren(out: Container[] = []): Container[] { const children = this.root.children; for (let i = 0; i < children.length; i++) { this._getChildren(children[i], out); } return out; } private _getChildren(container: Container, out: Container[] = []): Container[] { out.push(container); if (container.renderGroup) return out; const children = container.children; for (let i = 0; i < children.length; i++) { this._getChildren(children[i], out); } return out; } public invalidateMatrices() { this._matrixDirty = 0b111; } /** * Returns the inverse of the world transform matrix. * @returns {Matrix} The inverse of the world transform matrix. */ public get inverseWorldTransform() { if ((this._matrixDirty & 0b001) === 0) return this._inverseWorldTransform; this._matrixDirty &= ~0b001; // TODO - add dirty flag this._inverseWorldTransform ||= new Matrix(); return this._inverseWorldTransform .copyFrom(this.worldTransform) .invert(); } /** * Returns the inverse of the texture offset transform matrix. * @returns {Matrix} The inverse of the texture offset transform matrix. */ public get textureOffsetInverseTransform() { if ((this._matrixDirty & 0b010) === 0) return this._textureOffsetInverseTransform; this._matrixDirty &= ~0b010; this._textureOffsetInverseTransform ||= new Matrix(); // TODO shared.. bad! return this._textureOffsetInverseTransform .copyFrom(this.inverseWorldTransform) .translate( -this._textureBounds.x, -this._textureBounds.y ); } /** * Returns the inverse of the parent texture transform matrix. * This is used to properly transform coordinates when rendering into cached textures. * @returns {Matrix} The inverse of the parent texture transform matrix. */ public get inverseParentTextureTransform() { if ((this._matrixDirty & 0b100) === 0) return this._inverseParentTextureTransform; this._matrixDirty &= ~0b100; const parentCacheAsTexture = this._parentCacheAsTextureRenderGroup; if (parentCacheAsTexture) { this._inverseParentTextureTransform ||= new Matrix(); // Get relative transform by removing parent's world transform return this._inverseParentTextureTransform .copyFrom(this.worldTransform) .prepend(parentCacheAsTexture.inverseWorldTransform) // Offset by texture bounds .translate( -parentCacheAsTexture._textureBounds.x, -parentCacheAsTexture._textureBounds.y ); } return this.worldTransform; } /** * Returns a matrix that transforms coordinates to the correct coordinate space of the texture being rendered to. * This is the texture offset inverse transform of the closest parent RenderGroup that is cached as a texture. * @returns {Matrix | null} The transform matrix for the cached texture coordinate space, * or null if no parent is cached as texture. */ public get cacheToLocalTransform() { if (this.isCachedAsTexture) { return this.textureOffsetInverseTransform; } if (!this._parentCacheAsTextureRenderGroup) return null; return this._parentCacheAsTextureRenderGroup.textureOffsetInverseTransform; } }