diff --git a/packages/accessibility/src/AccessibilityManager.ts b/packages/accessibility/src/AccessibilityManager.ts index 68cf83b70a..4327e7df9e 100644 --- a/packages/accessibility/src/AccessibilityManager.ts +++ b/packages/accessibility/src/AccessibilityManager.ts @@ -4,8 +4,8 @@ import { accessibleTarget } from './accessibleTarget'; import type { Rectangle } from '@pixi/math'; import type { Container } from '@pixi/display'; -import type { Renderer, AbstractRenderer } from '@pixi/core'; import type { IAccessibleHTMLElement } from './accessibleTarget'; +import type { IRenderer } from '@pixi/core'; // add some extra variables to the container.. DisplayObject.mixin(accessibleTarget); @@ -42,7 +42,7 @@ export class AccessibilityManager * The renderer this accessibility manager works for. * @type {PIXI.CanvasRenderer|PIXI.Renderer} */ - public renderer: AbstractRenderer | Renderer; + public renderer: IRenderer; /** Internal variable, see isActive getter. */ private _isActive = false; @@ -74,7 +74,7 @@ export class AccessibilityManager /** * @param {PIXI.CanvasRenderer|PIXI.Renderer} renderer - A reference to the current renderer */ - constructor(renderer: AbstractRenderer | Renderer) + constructor(renderer: IRenderer) { this._hookDiv = null; @@ -270,15 +270,15 @@ export class AccessibilityManager this.androidUpdateCount = now + this.androidUpdateFrequency; - if (!(this.renderer as Renderer).renderingToScreen) + if (!this.renderer.renderingToScreen) { return; } // update children... - if (this.renderer._lastObjectRendered) + if (this.renderer.lastObjectRendered) { - this.updateAccessibleObjects(this.renderer._lastObjectRendered as Container); + this.updateAccessibleObjects(this.renderer.lastObjectRendered as Container); } const { left, top, width, height } = this.renderer.view.getBoundingClientRect(); diff --git a/packages/app/src/Application.ts b/packages/app/src/Application.ts index 22faa25851..1fddf38952 100644 --- a/packages/app/src/Application.ts +++ b/packages/app/src/Application.ts @@ -2,7 +2,7 @@ import { Container } from '@pixi/display'; import { autoDetectRenderer } from '@pixi/core'; import type { Rectangle } from '@pixi/math'; -import type { Renderer, IRendererOptionsAuto, AbstractRenderer } from '@pixi/core'; +import type { IRendererOptionsAuto, IRenderer } from '@pixi/core'; import type { IDestroyOptions } from '@pixi/display'; /** @@ -59,7 +59,7 @@ export class Application * WebGL renderer if available, otherwise CanvasRenderer. * @member {PIXI.Renderer|PIXI.CanvasRenderer} */ - public renderer: Renderer | AbstractRenderer; + public renderer: IRenderer; /** * @param {object} [options] - The optional renderer parameters. diff --git a/packages/canvas-display/src/Container.ts b/packages/canvas-display/src/Container.ts index 7c177d33e2..e2c4954831 100644 --- a/packages/canvas-display/src/Container.ts +++ b/packages/canvas-display/src/Container.ts @@ -30,7 +30,7 @@ Container.prototype.renderCanvas = function renderCanvas(renderer: CanvasRendere if (this._mask) { - renderer.maskManager.pushMask(this._mask as MaskData); + renderer.mask.pushMask(this._mask as MaskData); } this._renderCanvas(renderer); @@ -41,6 +41,6 @@ Container.prototype.renderCanvas = function renderCanvas(renderer: CanvasRendere if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } }; diff --git a/packages/canvas-extract/src/CanvasExtract.ts b/packages/canvas-extract/src/CanvasExtract.ts index 36c4e6ad6f..eb61c1f708 100644 --- a/packages/canvas-extract/src/CanvasExtract.ts +++ b/packages/canvas-extract/src/CanvasExtract.ts @@ -92,8 +92,8 @@ export class CanvasExtract } else { - context = renderer.rootContext; - resolution = renderer.resolution; + context = renderer.canvasContext.rootContext; + resolution = renderer._view.resolution; frame = TEMP_RECT; frame.width = this.renderer.width; frame.height = this.renderer.height; @@ -146,7 +146,7 @@ export class CanvasExtract } else { - context = renderer.rootContext; + context = renderer.canvasContext.rootContext; resolution = renderer.resolution; frame = TEMP_RECT; frame.width = renderer.width; diff --git a/packages/canvas-graphics/src/CanvasGraphicsRenderer.ts b/packages/canvas-graphics/src/CanvasGraphicsRenderer.ts index a3796f79f3..6e0c9ad49a 100644 --- a/packages/canvas-graphics/src/CanvasGraphicsRenderer.ts +++ b/packages/canvas-graphics/src/CanvasGraphicsRenderer.ts @@ -77,12 +77,13 @@ export class CanvasGraphicsRenderer public render(graphics: Graphics): void { const renderer = this.renderer; - const context = renderer.context; + + const context = renderer.canvasContext.activeContext; const worldAlpha = graphics.worldAlpha; const transform = graphics.transform.worldTransform; - renderer.setContextTransform(transform); - renderer.setBlendMode(graphics.blendMode); + renderer.canvasContext.setContextTransform(transform); + renderer.canvasContext.setBlendMode(graphics.blendMode); const graphicsData = graphics.geometry.graphicsData; @@ -105,7 +106,7 @@ export class CanvasGraphicsRenderer if (data.matrix) { - renderer.setContextTransform(transform.copyTo(this._tempMatrix).append(data.matrix)); + renderer.canvasContext.setContextTransform(transform.copyTo(this._tempMatrix).append(data.matrix)); } if (fillStyle.visible) diff --git a/packages/canvas-mesh/src/CanvasMeshRenderer.ts b/packages/canvas-mesh/src/CanvasMeshRenderer.ts index 4a8b976a60..1c3ebb92bd 100644 --- a/packages/canvas-mesh/src/CanvasMeshRenderer.ts +++ b/packages/canvas-mesh/src/CanvasMeshRenderer.ts @@ -31,9 +31,9 @@ export class CanvasMeshRenderer const renderer = this.renderer; const transform = mesh.worldTransform; - renderer.context.globalAlpha = mesh.worldAlpha; - renderer.setBlendMode(mesh.blendMode); - renderer.setContextTransform(transform, mesh.roundPixels); + renderer.canvasContext.activeContext.globalAlpha = mesh.worldAlpha; + renderer.canvasContext.setBlendMode(mesh.blendMode); + renderer.canvasContext.setContextTransform(transform, mesh.roundPixels); if (mesh.drawMode !== DRAW_MODES.TRIANGLES) { @@ -96,7 +96,7 @@ export class CanvasMeshRenderer */ private _renderDrawTriangle(mesh: Mesh, index0: number, index1: number, index2: number): void { - const context = this.renderer.context; + const context = this.renderer.canvasContext.activeContext; const vertices = mesh.geometry.buffers[0].data; const { uvs, texture } = mesh; @@ -138,7 +138,7 @@ export class CanvasMeshRenderer let y1 = vertices[index1 + 1]; let y2 = vertices[index2 + 1]; - const screenPadding = mesh.canvasPadding / this.renderer.resolution; + const screenPadding = mesh.canvasPadding / this.renderer.canvasContext.activeResolution; if (screenPadding > 0) { @@ -228,7 +228,7 @@ export class CanvasMeshRenderer ); context.restore(); - this.renderer.invalidateBlendMode(); + this.renderer.canvasContext.invalidateBlendMode(); } /** @@ -238,7 +238,7 @@ export class CanvasMeshRenderer */ renderMeshFlat(mesh: Mesh): void { - const context = this.renderer.context; + const context = this.renderer.canvasContext.activeContext; const vertices = mesh.geometry.getBuffer('aVertexPosition').data; const length = vertices.length / 2; diff --git a/packages/canvas-mesh/src/NineSlicePlane.ts b/packages/canvas-mesh/src/NineSlicePlane.ts index 0c21f0f84a..8502b0d15e 100644 --- a/packages/canvas-mesh/src/NineSlicePlane.ts +++ b/packages/canvas-mesh/src/NineSlicePlane.ts @@ -36,7 +36,7 @@ NineSlicePlane.prototype._canvasUvs = null; */ NineSlicePlane.prototype._renderCanvas = function _renderCanvas(renderer: CanvasRenderer): void { - const context = renderer.context; + const context = renderer.canvasContext.activeContext; const transform = this.worldTransform; const isTinted = this.tint !== 0xFFFFFF; const texture = this.texture; @@ -88,8 +88,8 @@ NineSlicePlane.prototype._renderCanvas = function _renderCanvas(renderer: Canvas } context.globalAlpha = this.worldAlpha; - renderer.setBlendMode(this.blendMode); - renderer.setContextTransform(transform, this.roundPixels); + renderer.canvasContext.setBlendMode(this.blendMode); + renderer.canvasContext.setContextTransform(transform, this.roundPixels); for (let row = 0; row < 3; row++) { diff --git a/packages/canvas-particle-container/src/ParticleContainer.ts b/packages/canvas-particle-container/src/ParticleContainer.ts index deb7f9e418..84045ddf40 100644 --- a/packages/canvas-particle-container/src/ParticleContainer.ts +++ b/packages/canvas-particle-container/src/ParticleContainer.ts @@ -16,7 +16,7 @@ ParticleContainer.prototype.renderCanvas = function renderCanvas(renderer: Canva return; } - const context = renderer.context; + const context = renderer.canvasContext.activeContext; const transform = this.worldTransform; let isRotated = true; @@ -26,7 +26,7 @@ ParticleContainer.prototype.renderCanvas = function renderCanvas(renderer: Canva let finalWidth = 0; let finalHeight = 0; - renderer.setBlendMode(this.blendMode); + renderer.canvasContext.setBlendMode(this.blendMode); context.globalAlpha = this.worldAlpha; @@ -55,7 +55,7 @@ ParticleContainer.prototype.renderCanvas = function renderCanvas(renderer: Canva // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call if (isRotated) { - renderer.setContextTransform(transform, false, 1); + renderer.canvasContext.setContextTransform(transform, false, 1); isRotated = false; } @@ -76,7 +76,7 @@ ParticleContainer.prototype.renderCanvas = function renderCanvas(renderer: Canva const childTransform = child.worldTransform; - renderer.setContextTransform(childTransform, this.roundPixels, 1); + renderer.canvasContext.setContextTransform(childTransform, this.roundPixels, 1); positionX = ((child.anchor.x) * (-frame.width)) + 0.5; positionY = ((child.anchor.y) * (-frame.height)) + 0.5; @@ -86,6 +86,7 @@ ParticleContainer.prototype.renderCanvas = function renderCanvas(renderer: Canva } const resolution = child._texture.baseTexture.resolution; + const contextResolution = renderer.canvasContext.activeResolution; context.drawImage( child._texture.baseTexture.getDrawableSource(), @@ -93,10 +94,10 @@ ParticleContainer.prototype.renderCanvas = function renderCanvas(renderer: Canva frame.y * resolution, frame.width * resolution, frame.height * resolution, - positionX * renderer.resolution, - positionY * renderer.resolution, - finalWidth * renderer.resolution, - finalHeight * renderer.resolution + positionX * contextResolution, + positionY * contextResolution, + finalWidth * contextResolution, + finalHeight * contextResolution ); } }; diff --git a/packages/canvas-prepare/src/CanvasPrepare.ts b/packages/canvas-prepare/src/CanvasPrepare.ts index 80c9f39b65..b566dd6029 100644 --- a/packages/canvas-prepare/src/CanvasPrepare.ts +++ b/packages/canvas-prepare/src/CanvasPrepare.ts @@ -1,7 +1,6 @@ -import { BaseTexture } from '@pixi/core'; +import { BaseTexture, IRenderer } from '@pixi/core'; import { BasePrepare } from '@pixi/prepare'; -import type { AbstractRenderer } from '@pixi/core'; import type { CanvasRenderer } from '@pixi/canvas-renderer'; import type { IDisplayObjectExtended } from '@pixi/prepare'; @@ -14,7 +13,7 @@ const CANVAS_START_SIZE = 16; * @param item - Item to check * @returns If item was uploaded. */ -function uploadBaseTextures(prepare: AbstractRenderer | BasePrepare, item: IDisplayObjectExtended): boolean +function uploadBaseTextures(prepare: IRenderer | BasePrepare, item: IDisplayObjectExtended): boolean { const tempPrepare = prepare as CanvasPrepare; diff --git a/packages/canvas-renderer/src/CanvasContextSystem.ts b/packages/canvas-renderer/src/CanvasContextSystem.ts new file mode 100644 index 0000000000..ddcfd02d2d --- /dev/null +++ b/packages/canvas-renderer/src/CanvasContextSystem.ts @@ -0,0 +1,222 @@ +import { Matrix } from '@pixi/math'; + +import type { CanvasRenderer } from './CanvasRenderer'; +import type { ISystem } from '@pixi/core'; +import { mapCanvasBlendModesToPixi } from './utils/mapCanvasBlendModesToPixi'; +import { BLEND_MODES, SCALE_MODES } from '@pixi/constants'; +import { settings } from '@pixi/settings'; + +const tempMatrix = new Matrix(); + +/** + * Rendering context for all browsers. This includes platform-specific + * properties that are not included in the spec for CanvasRenderingContext2D + * @private + */ +export interface CrossPlatformCanvasRenderingContext2D extends CanvasRenderingContext2D +{ + webkitImageSmoothingEnabled: boolean; + mozImageSmoothingEnabled: boolean; + oImageSmoothingEnabled: boolean; + msImageSmoothingEnabled: boolean; +} + +/* + * Different browsers support different smoothing property names + * this is the list of all platform props. + */ +export type SmoothingEnabledProperties = + 'imageSmoothingEnabled' | + 'webkitImageSmoothingEnabled' | + 'mozImageSmoothingEnabled' | + 'oImageSmoothingEnabled' | + 'msImageSmoothingEnabled'; + +/** + * System that manages the canvas `2d` contexts + * @memberof PIXI + */ +export class CanvasContextSystem implements ISystem +{ + /** A reference to the current renderer */ + private renderer: CanvasRenderer; + + /** The root canvas 2d context that everything is drawn with. */ + public rootContext: CrossPlatformCanvasRenderingContext2D; + /** The currently active canvas 2d context (could change with renderTextures) */ + public activeContext: CrossPlatformCanvasRenderingContext2D; + public activeResolution = 1; + + /** The canvas property used to set the canvas smoothing property. */ + public smoothProperty: SmoothingEnabledProperties = 'imageSmoothingEnabled'; + /** Tracks the blend modes useful for this renderer. */ + public readonly blendModes: string[] = mapCanvasBlendModesToPixi(); + + _activeBlendMode: BLEND_MODES = null; + /** Projection transform, passed in render() stored here */ + _projTransform: Matrix = null; + + /** @private */ + _outerBlend = false; + + /** @param renderer - A reference to the current renderer */ + constructor(renderer: CanvasRenderer) + { + this.renderer = renderer; + } + + /** initiates the system */ + init(): void + { + const alpha = this.renderer.background.alpha < 1; + + this.rootContext = this.renderer.view.getContext('2d', { alpha }) as + CrossPlatformCanvasRenderingContext2D; + + this.activeContext = this.rootContext; + + if (!this.rootContext.imageSmoothingEnabled) + { + const rc = this.rootContext; + + if (rc.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (rc.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (rc.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (rc.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + } + + /** + * Sets matrix of context. + * called only from render() methods + * takes care about resolution + * @param transform - world matrix of current element + * @param roundPixels - whether to round (tx,ty) coords + * @param localResolution - If specified, used instead of `renderer.resolution` for local scaling + */ + setContextTransform(transform: Matrix, roundPixels?: boolean, localResolution?: number): void + { + let mat = transform; + const proj = this._projTransform; + const contextResolution = this.activeResolution; + + localResolution = localResolution || contextResolution; + + if (proj) + { + mat = tempMatrix; + mat.copyFrom(transform); + mat.prepend(proj); + } + + if (roundPixels) + { + this.activeContext.setTransform( + mat.a * localResolution, + mat.b * localResolution, + mat.c * localResolution, + mat.d * localResolution, + (mat.tx * contextResolution) | 0, + (mat.ty * contextResolution) | 0 + ); + } + else + { + this.activeContext.setTransform( + mat.a * localResolution, + mat.b * localResolution, + mat.c * localResolution, + mat.d * localResolution, + mat.tx * contextResolution, + mat.ty * contextResolution + ); + } + } + + /** + * Clear the canvas of renderer. + * @param {string} [clearColor] - Clear the canvas with this color, except the canvas is transparent. + * @param {number} [alpha] - Alpha to apply to the background fill color. + */ + public clear(clearColor?: string, alpha?: number): void + { + const { activeContext: context, renderer } = this; + + clearColor = clearColor ?? this.renderer.background.colorString; + + context.clearRect(0, 0, renderer.width, renderer.height); + + if (clearColor) + { + context.globalAlpha = alpha ?? this.renderer.background.alpha; + context.fillStyle = clearColor; + context.fillRect(0, 0, renderer.width, renderer.height); + context.globalAlpha = 1; + } + } + + /** + * Sets the blend mode of the renderer. + * @param {number} blendMode - See {@link PIXI.BLEND_MODES} for valid values. + * @param {boolean} [readyForOuterBlend=false] - Some blendModes are dangerous, they affect outer space of sprite. + * Pass `true` only if you are ready to use them. + */ + setBlendMode(blendMode: BLEND_MODES, readyForOuterBlend?: boolean): void + { + const outerBlend = blendMode === BLEND_MODES.SRC_IN + || blendMode === BLEND_MODES.SRC_OUT + || blendMode === BLEND_MODES.DST_IN + || blendMode === BLEND_MODES.DST_ATOP; + + if (!readyForOuterBlend && outerBlend) + { + blendMode = BLEND_MODES.NORMAL; + } + + if (this._activeBlendMode === blendMode) + { + return; + } + + this._activeBlendMode = blendMode; + this._outerBlend = outerBlend; + this.activeContext.globalCompositeOperation = this.blendModes[blendMode]; + } + + resize(): void + { + // reset the scale mode.. oddly this seems to be reset when the canvas is resized. + // surely a browser bug?? Let PixiJS fix that for you.. + if (this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (settings.SCALE_MODE === SCALE_MODES.LINEAR); + } + } + + /** Checks if blend mode has changed. */ + invalidateBlendMode(): void + { + this._activeBlendMode = this.blendModes.indexOf(this.activeContext.globalCompositeOperation); + } + + public destroy(): void + { + this.renderer = null; + this.rootContext = null; + + this.activeContext = null; + this.smoothProperty = null; + } +} diff --git a/packages/canvas-renderer/src/utils/CanvasMaskManager.ts b/packages/canvas-renderer/src/CanvasMaskSystem.ts similarity index 94% rename from packages/canvas-renderer/src/utils/CanvasMaskManager.ts rename to packages/canvas-renderer/src/CanvasMaskSystem.ts index 737f33914d..e29b66f188 100644 --- a/packages/canvas-renderer/src/utils/CanvasMaskManager.ts +++ b/packages/canvas-renderer/src/CanvasMaskSystem.ts @@ -1,8 +1,8 @@ import { Polygon, SHAPES } from '@pixi/math'; -import type { CanvasRenderer } from '../CanvasRenderer'; +import type { CanvasRenderer } from './CanvasRenderer'; import type { Graphics } from '@pixi/graphics'; -import type { MaskData } from '@pixi/core'; +import type { ISystem, MaskData } from '@pixi/core'; import type { Container } from '@pixi/display'; /** @@ -12,7 +12,7 @@ import type { Container } from '@pixi/display'; * @class * @memberof PIXI */ -export class CanvasMaskManager +export class CanvasMaskSystem implements ISystem { /** A reference to the current renderer */ private renderer: CanvasRenderer; @@ -33,7 +33,7 @@ export class CanvasMaskManager const renderer = this.renderer; const maskObject = ((maskData as MaskData).maskObject || maskData) as Container; - renderer.context.save(); + renderer.canvasContext.activeContext.save(); // TODO support sprite alpha masks?? // lots of effort required. If demand is great enough.. @@ -43,7 +43,7 @@ export class CanvasMaskManager this.recursiveFindShapes(maskObject, foundShapes); if (foundShapes.length > 0) { - const { context } = renderer; + const context = renderer.canvasContext.activeContext; context.beginPath(); @@ -52,7 +52,7 @@ export class CanvasMaskManager const shape = foundShapes[i]; const transform = shape.transform.worldTransform; - this.renderer.setContextTransform(transform); + this.renderer.canvasContext.setContextTransform(transform); this.renderGraphicsShape(shape); } @@ -93,7 +93,7 @@ export class CanvasMaskManager { graphics.finishPoly(); - const context = this.renderer.context; + const context = this.renderer.canvasContext.activeContext; const graphicsData = graphics.geometry.graphicsData; const len = graphicsData.length; @@ -250,8 +250,8 @@ export class CanvasMaskManager */ popMask(renderer: CanvasRenderer): void { - renderer.context.restore(); - renderer.invalidateBlendMode(); + renderer.canvasContext.activeContext.restore(); + renderer.canvasContext.invalidateBlendMode(); } /** Destroys this canvas mask manager. */ diff --git a/packages/canvas-renderer/src/CanvasObjectRendererSystem.ts b/packages/canvas-renderer/src/CanvasObjectRendererSystem.ts new file mode 100644 index 0000000000..59e85fd44d --- /dev/null +++ b/packages/canvas-renderer/src/CanvasObjectRendererSystem.ts @@ -0,0 +1,183 @@ +import { Matrix } from '@pixi/math'; + +import type { CanvasRenderer } from './CanvasRenderer'; +import { BaseRenderTexture, CanvasResource, IRendererRenderOptions, ISystem, RenderTexture } from '@pixi/core'; +import { BLEND_MODES } from '@pixi/constants'; +import { CanvasRenderTarget, deprecation, hex2string, rgb2hex } from '@pixi/utils'; +import { DisplayObject } from 'pixi.js'; +import { CrossPlatformCanvasRenderingContext2D } from './CanvasContextSystem'; + +/** + * system that provides a render function that focussing on rendering Pixi Scene Graph objects + * to either the main view or to a renderTexture. Used for Canvas `2d` contexts + * @memberof PIXI + */ +export class CanvasObjectRendererSystem implements ISystem +{ + /** A reference to the current renderer */ + private renderer: CanvasRenderer; + renderingToScreen: boolean; + lastObjectRendered: DisplayObject; + + /** @param renderer - A reference to the current renderer */ + constructor(renderer: CanvasRenderer) + { + this.renderer = renderer; + } + + /** + * Renders the object to its Canvas view. + * @param displayObject - The object to be rendered. + * @param options - the options to be passed to the renderer + */ + public render(displayObject: DisplayObject, options?: IRendererRenderOptions | RenderTexture | BaseRenderTexture): void + { + const renderer = this.renderer; + + if (!renderer.view) + { + return; + } + + const _context = renderer.canvasContext; + + let renderTexture: BaseRenderTexture | RenderTexture; + let clear: boolean; + let transform: Matrix; + let skipUpdateTransform: boolean; + + if (options) + { + if (options instanceof RenderTexture || options instanceof BaseRenderTexture) + { + // #if _DEBUG + deprecation('6.0.0', 'CanvasRenderer#render arguments changed, use options instead.'); + // #endif + + /* eslint-disable prefer-rest-params */ + renderTexture = options; + clear = arguments[2]; + transform = arguments[3]; + skipUpdateTransform = arguments[4]; + /* eslint-enable prefer-rest-params */ + } + else + { + renderTexture = options.renderTexture; + clear = options.clear; + transform = options.transform; + skipUpdateTransform = options.skipUpdateTransform; + } + } + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + renderer.emit('prerender'); + + const rootResolution = renderer.resolution; + + if (renderTexture) + { + renderTexture = renderTexture.castToBaseTexture() as BaseRenderTexture; + + if (!renderTexture._canvasRenderTarget) + { + renderTexture._canvasRenderTarget = new CanvasRenderTarget( + renderTexture.width, + renderTexture.height, + renderTexture.resolution + ); + + renderTexture.resource = new CanvasResource(renderTexture._canvasRenderTarget.canvas); + renderTexture.valid = true; + } + + _context.activeContext = renderTexture._canvasRenderTarget.context as CrossPlatformCanvasRenderingContext2D; + renderer.canvasContext.activeResolution = renderTexture._canvasRenderTarget.resolution; + } + else + { + _context.activeContext = _context.rootContext; + _context.activeResolution = rootResolution; + } + + const context2D = _context.activeContext; + + _context._projTransform = transform || null; + + if (!renderTexture) + { + this.lastObjectRendered = displayObject; + } + + if (!skipUpdateTransform) + { + // update the scene graph + const cacheParent = displayObject.enableTempParent(); + + displayObject.updateTransform(); + displayObject.disableTempParent(cacheParent); + } + + context2D.save(); + context2D.setTransform(1, 0, 0, 1, 0, 0); + context2D.globalAlpha = 1; + _context._activeBlendMode = BLEND_MODES.NORMAL; + _context._outerBlend = false; + context2D.globalCompositeOperation = _context.blendModes[BLEND_MODES.NORMAL]; + + if (clear !== undefined ? clear : renderer.background.clearBeforeRender) + { + if (this.renderingToScreen) + { + context2D.clearRect(0, 0, renderer.width, renderer.height); + + const background = renderer.background; + + if (background.alpha > 0) + { + context2D.globalAlpha = background.alpha; + context2D.fillStyle = background.colorString; + context2D.fillRect(0, 0, renderer.width, renderer.height); + context2D.globalAlpha = 1; + } + } + else + { + renderTexture = (renderTexture as BaseRenderTexture); + renderTexture._canvasRenderTarget.clear(); + + const clearColor = renderTexture.clearColor; + + if (clearColor[3] > 0) + { + context2D.globalAlpha = clearColor[3] ?? 1; + context2D.fillStyle = hex2string(rgb2hex(clearColor)); + context2D.fillRect(0, 0, renderTexture.realWidth, renderTexture.realHeight); + context2D.globalAlpha = 1; + } + } + } + + // TODO RENDER TARGET STUFF HERE.. + const tempContext = _context.activeContext; + + _context.activeContext = context2D; + displayObject.renderCanvas(renderer); + _context.activeContext = tempContext; + + context2D.restore(); + + _context.activeResolution = rootResolution; + _context._projTransform = null; + + renderer.emit('postrender'); + } + + public destroy(): void + { + this.lastObjectRendered = null; + this.render = null; + } +} diff --git a/packages/canvas-renderer/src/CanvasRenderer.ts b/packages/canvas-renderer/src/CanvasRenderer.ts index e4b12d1cc9..cb3c4b0677 100644 --- a/packages/canvas-renderer/src/CanvasRenderer.ts +++ b/packages/canvas-renderer/src/CanvasRenderer.ts @@ -1,10 +1,19 @@ -import { AbstractRenderer, CanvasResource, RenderTexture, BaseRenderTexture } from '@pixi/core'; -import { CanvasRenderTarget, sayHello, rgb2hex, hex2string, deprecation } from '@pixi/utils'; -import { CanvasMaskManager } from './utils/CanvasMaskManager'; -import { mapCanvasBlendModesToPixi } from './utils/mapCanvasBlendModesToPixi'; -import { RENDERER_TYPE, SCALE_MODES, BLEND_MODES } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { Matrix } from '@pixi/math'; +import { + RenderTexture, + BaseRenderTexture, + IRenderableObject, + GenerateTextureSystem, + SystemManager, IRenderer, + BackgroundSystem, + ViewSystem, + PluginSystem, + StartupSystem, + StartupOptions, + IGenerateTextureOptions +} from '@pixi/core'; +import { CanvasMaskSystem } from './CanvasMaskSystem'; +import { BLEND_MODES, RENDERER_TYPE, SCALE_MODES } from '@pixi/constants'; +import { Matrix, Rectangle } from '@pixi/math'; import type { DisplayObject } from '@pixi/display'; import type { @@ -14,52 +23,50 @@ import type { IRendererRenderOptions } from '@pixi/core'; -const tempMatrix = new Matrix(); +import { CanvasContextSystem, SmoothingEnabledProperties } from './CanvasContextSystem'; +import { CanvasObjectRendererSystem } from './CanvasObjectRendererSystem'; +import { settings } from '@pixi/settings'; +import { deprecation } from '@pixi/utils'; export interface ICanvasRendererPluginConstructor { new (renderer: CanvasRenderer, options?: any): IRendererPlugin; } -export interface ICanvasRendererPlugins -{ - [key: string]: any; -} - -/* - * Different browsers support different smoothing property names - * this is the list of all platform props. - */ -type SmoothingEnabledProperties = - 'imageSmoothingEnabled' | - 'webkitImageSmoothingEnabled' | - 'mozImageSmoothingEnabled' | - 'oImageSmoothingEnabled' | - 'msImageSmoothingEnabled'; - -/** - * Rendering context for all browsers. This includes platform-specific - * properties that are not included in the spec for CanvasRenderingContext2D - * @private - */ -export interface CrossPlatformCanvasRenderingContext2D extends CanvasRenderingContext2D -{ - webkitImageSmoothingEnabled: boolean; - mozImageSmoothingEnabled: boolean; - oImageSmoothingEnabled: boolean; - msImageSmoothingEnabled: boolean; -} - /** * The CanvasRenderer draws the scene and all its content onto a 2d canvas. * + * This renderer should be used for browsers that support WebGL. + * * This renderer should be used for browsers that do not support WebGL. - * Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything! + * Don't forget to add the view to your DOM or you will not see anything! + * + * Renderer is composed of systems that manage specific tasks. The following systems are added by default + * whenever you create a renderer: + * + * | System | Description | + * | ------------------------------------ | ----------------------------------------------------------------------------- | + * + * | Generic Systems | Systems that manage functionality that all renderer types share | + * | ------------------------------------ | ----------------------------------------------------------------------------- | + * | {@link PIXI.ViewSystem} | This manages the main view of the renderer usually a Canvas | + * | {@link PIXI.PluginSystem} | This manages plugins for the renderer | + * | {@link PIXI.BackgroundSystem} | This manages the main views background color and alpha | + * | {@link PIXI.StartupSystem} | Boots up a renderer and initiatives all the systems | + * | {@link PIXI.EventSystem} | This manages UI events. | + * | {@link PIXI.GenerateTextureSystem} | This adds the ability to generate textures from any PIXI.DisplayObject | + * + * | Pixi high level Systems | Set of Pixi specific systems designed to work with Pixi objects | + * | ------------------------------------ | ----------------------------------------------------------------------------- | + * | {@link PIXI.CanvasContextSystem} | This manages the canvas `2d` contexts and their state | + * | {@link PIXI.CanvasMaskSystem} | This manages masking operations. | + * | {@link PIXI.CanvasRenderSystem} | This adds the ability to render a PIXI.DisplayObject | + * + * The breadth of the API surface provided by the renderer is contained within these systems. * @class * @memberof PIXI - * @extends PIXI.AbstractRenderer */ -export class CanvasRenderer extends AbstractRenderer +export class CanvasRenderer extends SystemManager implements IRenderer { /** * Fired after rendering finishes. @@ -70,29 +77,64 @@ export class CanvasRenderer extends AbstractRenderer * @event PIXI.CanvasRenderer#prerender */ - /** The root canvas 2d context that everything is drawn with. */ - public readonly rootContext: CrossPlatformCanvasRenderingContext2D; - /** The currently active canvas 2d context (could change with renderTextures) */ - public context: CrossPlatformCanvasRenderingContext2D; - /** Boolean flag controlling canvas refresh. */ - public refresh = true; /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * @member {PIXI.CanvasMaskManager} + * The type of the renderer. will be PIXI.RENDERER_TYPE.CANVAS + * @member {number} + * @see PIXI.RENDERER_TYPE */ - public maskManager: CanvasMaskManager = new CanvasMaskManager(this); - /** The canvas property used to set the canvas smoothing property. */ - public smoothProperty: SmoothingEnabledProperties = 'imageSmoothingEnabled'; - /** Tracks the blend modes useful for this renderer. */ - public readonly blendModes: string[] = mapCanvasBlendModesToPixi(); - public renderingToScreen = false; + public readonly type: RENDERER_TYPE.CANVAS; - private _activeBlendMode: BLEND_MODES = null; - /** Projection transform, passed in render() stored here */ - private _projTransform: Matrix = null; + /** When logging Pixi to the console, this is the name we will show */ + public readonly rendererLogId = 'Canvas'; - /** @private */ - _outerBlend = false; + // systems.. + /** + * textureGenerator system instance + * @readonly + */ + public textureGenerator: GenerateTextureSystem; + + /** + * background system instance + * @readonly + */ + public background: BackgroundSystem; + + /** + * canvas mask system instance + * @readonly + */ + public mask: CanvasMaskSystem; + + /** + * plugin system instance + * @readonly + */ + public _plugin: PluginSystem; + + /** + * Canvas context system instance + * @readonly + */ + public canvasContext: CanvasContextSystem; + + /** + * Startup system instance + * @readonly + */ + public startup: StartupSystem; + + /** + * View system instance + * @readonly + */ + public _view: ViewSystem; + + /** + * renderer system instance + * @readonly + */ + public objectRenderer: CanvasObjectRendererSystem; /** * @param options - The optional renderer parameters @@ -116,46 +158,102 @@ export class CanvasRenderer extends AbstractRenderer */ constructor(options?: IRendererOptions) { - super(RENDERER_TYPE.CANVAS, options); + super(); + + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); + + const systemConfig = { + runners: ['init', 'destroy', 'contextChange', 'reset', 'update', 'postrender', 'prerender', 'resize'], + systems: { + // systems shared by all renderers.. + textureGenerator: GenerateTextureSystem, + background: BackgroundSystem, + _view: ViewSystem, + _plugin: PluginSystem, + startup: StartupSystem, + + // canvas systems.. + mask: CanvasMaskSystem, + canvasContext: CanvasContextSystem, + objectRenderer: CanvasObjectRendererSystem, + } + }; + + this.setup(systemConfig); + + // convert our big blob of options into system specific ones.. + const startupOptions: StartupOptions = { + _plugin: CanvasRenderer.__plugins, + background: { + alpha: options.backgroundAlpha, + color: options.backgroundColor, + clearBeforeRender: options.clearBeforeRender, + }, + _view: { + height: options.height, + width: options.width, + autoDensity: options.autoDensity, + resolution: options.resolution, + } + }; + + this.startup.run(startupOptions); + } - this.rootContext = this.view.getContext('2d', { alpha: this.useContextAlpha }) as - CrossPlatformCanvasRenderingContext2D; + /** + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. + * @param displayObject - The displayObject the object will be generated from. + * @param {object} options - Generate texture options. + * @param {PIXI.SCALE_MODES} options.scaleMode - The scale mode of the texture. + * @param {number} options.resolution - The resolution / device pixel ratio of the texture being generated. + * @param {PIXI.Rectangle} options.region - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @param {PIXI.MSAA_QUALITY} options.multisample - The number of samples of the frame buffer. + * @returns A texture of the graphics object. + */ + generateTexture(displayObject: IRenderableObject, options?: IGenerateTextureOptions): RenderTexture; - this.context = this.rootContext; + /** + * Please use the options argument instead. + * @deprecated Since 6.1.0 + * @param displayObject - The displayObject the object will be generated from. + * @param scaleMode - The scale mode of the texture. + * @param resolution - The resolution / device pixel ratio of the texture being generated. + * @param region - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @returns A texture of the graphics object. + */ + generateTexture( + displayObject: IRenderableObject, + scaleMode?: SCALE_MODES, + resolution?: number, + region?: Rectangle): RenderTexture; - if (!this.rootContext.imageSmoothingEnabled) + /** + * @ignore + */ + generateTexture(displayObject: IRenderableObject, + options: IGenerateTextureOptions | SCALE_MODES = {}, + resolution?: number, region?: Rectangle): RenderTexture + { + // @deprecated parameters spread, use options instead + if (typeof options === 'number') { - const rc = this.rootContext; + // #if _DEBUG + deprecation('6.1.0', 'generateTexture options (scaleMode, resolution, region) are now object options.'); + // #endif - if (rc.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (rc.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (rc.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (rc.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } + options = { scaleMode: options, resolution, region }; } - this.initPlugins(CanvasRenderer.__plugins); - - sayHello('Canvas'); - - this.resize(this.options.width, this.options.height); + return this.textureGenerator.generateTexture(displayObject, options); } - /** Adds a new system to the renderer. It does nothing in the CanvasRenderer. */ - addSystem(): this + reset(): void { - return this; + // nothing to be done :D } /** @@ -184,206 +282,199 @@ export class CanvasRenderer extends AbstractRenderer /** @ignore */ public render(displayObject: DisplayObject, options?: IRendererRenderOptions | RenderTexture | BaseRenderTexture): void { - if (!this.view) - { - return; - } + this.objectRenderer.render(displayObject, options); + } - let renderTexture: BaseRenderTexture | RenderTexture; - let clear: boolean; - let transform: Matrix; - let skipUpdateTransform: boolean; + /** Clear the canvas of renderer. */ + public clear(): void + { + this.canvasContext.clear(); + } - if (options) - { - if (options instanceof RenderTexture || options instanceof BaseRenderTexture) - { - // #if _DEBUG - deprecation('6.0.0', 'CanvasRenderer#render arguments changed, use options instead.'); - // #endif - - /* eslint-disable prefer-rest-params */ - renderTexture = options; - clear = arguments[2]; - transform = arguments[3]; - skipUpdateTransform = arguments[4]; - /* eslint-enable prefer-rest-params */ - } - else - { - renderTexture = options.renderTexture; - clear = options.clear; - transform = options.transform; - skipUpdateTransform = options.skipUpdateTransform; - } - } + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + */ + public destroy(removeView?: boolean): void + { + this.runners.destroy.items.reverse(); - // can be handy to know! - this.renderingToScreen = !renderTexture; + this.emitWithCustomOptions(this.runners.destroy, { + _view: removeView, + }); - this.emit('prerender'); + super.destroy(); + } - const rootResolution = this.resolution; + /** Collection of plugins */ + get plugins(): IRendererPlugins + { + return this._plugin.plugins; + } - if (renderTexture) - { - renderTexture = renderTexture.castToBaseTexture() as BaseRenderTexture; - - if (!renderTexture._canvasRenderTarget) - { - renderTexture._canvasRenderTarget = new CanvasRenderTarget( - renderTexture.width, - renderTexture.height, - renderTexture.resolution - ); - renderTexture.resource = new CanvasResource(renderTexture._canvasRenderTarget.canvas); - renderTexture.valid = true; - } + /** + * Resizes the canvas view to the specified width and height. + * @param desiredScreenWidth - the desired width of the screen + * @param desiredScreenHeight - the desired height of the screen + */ + public resize(desiredScreenWidth: number, desiredScreenHeight: number): void + { + this._view.resizeView(desiredScreenWidth, desiredScreenHeight); + } - this.context = renderTexture._canvasRenderTarget.context as CrossPlatformCanvasRenderingContext2D; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else - { - this.context = this.rootContext; - } + /** + * Same as view.width, actual number of pixels in the canvas by horizontal. + * @member {number} + * @readonly + * @default 800 + */ + get width(): number + { + return this._view.element.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical. + * @member {number} + * @readonly + * @default 600 + */ + get height(): number + { + return this._view.element.height; + } - const context = this.context; + /** The resolution / device pixel ratio of the renderer. */ + get resolution(): number + { + return this._view.resolution; + } - this._projTransform = transform || null; + /** Whether CSS dimensions of canvas view should be resized to screen dimensions automatically. */ + get autoDensity(): boolean + { + return this._view.autoDensity; + } - if (!renderTexture) - { - this._lastObjectRendered = displayObject; - } + /** The canvas element that everything is drawn to.*/ + get view(): HTMLCanvasElement + { + return this._view.element; + } - if (!skipUpdateTransform) - { - // update the scene graph - const cacheParent = displayObject.enableTempParent(); + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight). + * Its safe to use as filterArea or hitArea for the whole stage. + */ + get screen(): Rectangle + { + return this._view.screen; + } - displayObject.updateTransform(); - displayObject.disableTempParent(cacheParent); - } + /** the last object rendered by the renderer. Useful for other plugins like interaction managers */ + get lastObjectRendered(): IRenderableObject + { + return this.objectRenderer.lastObjectRendered; + } - context.save(); - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - this._activeBlendMode = BLEND_MODES.NORMAL; - this._outerBlend = false; - context.globalCompositeOperation = this.blendModes[BLEND_MODES.NORMAL]; + /** Flag if we are rendering to the screen vs renderTexture */ + get renderingToScreen(): boolean + { + return this.objectRenderer.renderingToScreen; + } - if (clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) - { - context.clearRect(0, 0, this.width, this.height); - - if (this.backgroundAlpha > 0) - { - context.globalAlpha = this.useContextAlpha ? this.backgroundAlpha : 1; - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); - context.globalAlpha = 1; - } - } - else - { - renderTexture = (renderTexture as BaseRenderTexture); - renderTexture._canvasRenderTarget.clear(); - - const clearColor = renderTexture.clearColor; - - if (clearColor[3] > 0) - { - context.globalAlpha = this.useContextAlpha ? clearColor[3] : 1; - context.fillStyle = hex2string(rgb2hex(clearColor)); - context.fillRect(0, 0, renderTexture.realWidth, renderTexture.realHeight); - context.globalAlpha = 1; - } - } - } + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent PixiJS will use a canvas sized fillRect operation every + * frame to set the canvas background color. If the scene is transparent PixiJS will use clearRect + * to clear the canvas every frame. Disable this by setting this to false. For example, if + * your game has a canvas filling background image you often don't need this set. + */ + get clearBeforeRender(): boolean + { + return this.background.clearBeforeRender; + } - // TODO RENDER TARGET STUFF HERE.. - const tempContext = this.context; + // deprecated zone.. - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; + /** + * Tracks the blend modes useful for this renderer. + * @deprecated since 6.4.0 use `renderer.canvasContext.blendModes` instead + */ + get blendModes(): string[] + { + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.blendModes has been deprecated, please use renderer.canvasContext.blendModes instead'); + // #endif - context.restore(); + return this.canvasContext.blendModes; + } - this.resolution = rootResolution; - this._projTransform = null; + /** + * system that manages canvas masks + * @deprecated since 6.4.0 use `renderer.canvasContext.mask` + */ + get maskManager(): CanvasMaskSystem + { + deprecation('6.4.0', 'renderer.maskManager has been deprecated, please use renderer.mask instead'); - this.emit('postrender'); + return this.mask; } /** - * Sets matrix of context. - * called only from render() methods - * takes care about resolution - * @param transform - world matrix of current element - * @param roundPixels - whether to round (tx,ty) coords - * @param localResolution - If specified, used instead of `renderer.resolution` for local scaling + * Boolean flag controlling canvas refresh. + * @deprecated since 6.4.0 */ - setContextTransform(transform: Matrix, roundPixels?: boolean, localResolution?: number): void + get refresh(): boolean { - let mat = transform; - const proj = this._projTransform; - const resolution = this.resolution; + // #if _DEBUG + deprecation('6.4.0', 'renderer.refresh has been deprecated'); + // #endif - localResolution = localResolution || resolution; + return true; + } - if (proj) - { - mat = tempMatrix; - mat.copyFrom(transform); - mat.prepend(proj); - } + /** + * The root canvas 2d context that everything is drawn with. + * @deprecated since 6.4.0 Use `renderer.canvasContext.rootContext instead + */ + get rootContext(): CanvasRenderingContext2D + { + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.rootContext has been deprecated, please use renderer.canvasContext.rootContext instead'); + // #endif - if (roundPixels) - { - this.context.setTransform( - mat.a * localResolution, - mat.b * localResolution, - mat.c * localResolution, - mat.d * localResolution, - (mat.tx * resolution) | 0, - (mat.ty * resolution) | 0 - ); - } - else - { - this.context.setTransform( - mat.a * localResolution, - mat.b * localResolution, - mat.c * localResolution, - mat.d * localResolution, - mat.tx * resolution, - mat.ty * resolution - ); - } + return this.canvasContext.rootContext; } /** - * Clear the canvas of renderer. - * @param {string} [clearColor] - Clear the canvas with this color, except the canvas is transparent. - * @param {number} [alpha] - Alpha to apply to the background fill color. + * The currently active canvas 2d context (could change with renderTextures) + * @deprecated since 6.4.0 Use `renderer.canvasContext.activeContext instead */ - public clear(clearColor: string = this._backgroundColorString, alpha: number = this.backgroundAlpha): void + get context(): CanvasRenderingContext2D { - const { context } = this; + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.context has been deprecated, please use renderer.canvasContext.activeContext instead'); + // #endif - context.clearRect(0, 0, this.width, this.height); + return this.canvasContext.activeContext; + } - if (clearColor) - { - context.globalAlpha = this.useContextAlpha ? alpha : 1; - context.fillStyle = clearColor; - context.fillRect(0, 0, this.width, this.height); - context.globalAlpha = 1; - } + /** + * The canvas property used to set the canvas smoothing property. + * @deprecated since 6.4.0 Use `renderer.canvasContext.smoothProperty` instead. + */ + get smoothProperty(): SmoothingEnabledProperties + { + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.smoothProperty has been deprecated, please use renderer.canvasContext.smoothProperty instead'); + // #endif + + return this.canvasContext.smoothProperty; } /** @@ -391,70 +482,123 @@ export class CanvasRenderer extends AbstractRenderer * @param {number} blendMode - See {@link PIXI.BLEND_MODES} for valid values. * @param {boolean} [readyForOuterBlend=false] - Some blendModes are dangerous, they affect outer space of sprite. * Pass `true` only if you are ready to use them. + * @deprecated since 6.4.0 Use `renderer.canvasContext.setBlendMode` instead. */ setBlendMode(blendMode: BLEND_MODES, readyForOuterBlend?: boolean): void { - const outerBlend = blendMode === BLEND_MODES.SRC_IN - || blendMode === BLEND_MODES.SRC_OUT - || blendMode === BLEND_MODES.DST_IN - || blendMode === BLEND_MODES.DST_ATOP; + // #if _DEBUG + deprecation('6.4.0', 'renderer.setBlendMode has been deprecated, use renderer.canvasContext.setBlendMode instead'); + // #endif - if (!readyForOuterBlend && outerBlend) - { - blendMode = BLEND_MODES.NORMAL; - } + this.canvasContext.setBlendMode(blendMode, readyForOuterBlend); + } - if (this._activeBlendMode === blendMode) - { - return; - } + /** + * Checks if blend mode has changed. + * @deprecated since 6.4.0 Use `renderer.canvasContext.invalidateBlendMode` instead. + */ + invalidateBlendMode(): void + { + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.invalidateBlendMode has been deprecated, use renderer.canvasContext.invalidateBlendMode instead'); + // #endif - this._activeBlendMode = blendMode; - this._outerBlend = outerBlend; - this.context.globalCompositeOperation = this.blendModes[blendMode]; + this.canvasContext.invalidateBlendMode(); } /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. + * Sets matrix of context. + * called only from render() methods + * takes care about resolution + * @param transform - world matrix of current element + * @param roundPixels - whether to round (tx,ty) coords + * @param localResolution - If specified, used instead of `renderer.resolution` for local scaling + * @deprecated since 6.4.0 - Use `renderer.canvasContext.setContextTransform` instead. */ - public destroy(removeView?: boolean): void + setContextTransform(transform: Matrix, roundPixels?: boolean, localResolution?: number): void { - // call the base destroy - super.destroy(removeView); + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.setContextTransform has been deprecated, use renderer.canvasContext.setContextTransform instead'); + // #endif - this.context = null; + this.canvasContext.setContextTransform(transform, roundPixels, localResolution); + } - this.refresh = true; + /** + * The background color to fill if not transparent + * @member {number} + * @deprecated since 6.4.0 + */ + get backgroundColor(): number + { + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.backgroundColor has been deprecated, use renderer.background.color instead.'); + // #endif - this.maskManager.destroy(); - this.maskManager = null; + return this.background.color; + } - this.smoothProperty = null; + set backgroundColor(value: number) + { + // #if _DEBUG + deprecation('6.4.0', 'renderer.backgroundColor has been deprecated, use renderer.background.color instead.'); + // #endif + + this.background.color = value; } /** - * Resizes the canvas view to the specified width and height. - * @extends PIXI.AbstractRenderer#resize - * @param desiredScreenWidth - the desired width of the screen - * @param desiredScreenHeight - the desired height of the screen + * The background color alpha. Setting this to 0 will make the canvas transparent. + * @member {number} + * @deprecated since 6.4.0 */ - public resize(desiredScreenWidth: number, desiredScreenHeight: number): void + get backgroundAlpha(): number { - super.resize(desiredScreenWidth, desiredScreenHeight); + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.backgroundAlpha has been deprecated, use renderer.background.alpha instead.'); + // #endif - // reset the scale mode.. oddly this seems to be reset when the canvas is resized. - // surely a browser bug?? Let PixiJS fix that for you.. - if (this.smoothProperty) - { - this.rootContext[this.smoothProperty] = (settings.SCALE_MODE === SCALE_MODES.LINEAR); - } + return this.background.color; } - /** Checks if blend mode has changed. */ - invalidateBlendMode(): void + set backgroundAlpha(value: number) { - this._activeBlendMode = this.blendModes.indexOf(this.context.globalCompositeOperation); + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.backgroundAlpha has been deprecated, use renderer.background.alpha instead.'); + // #endif + + this.background.alpha = value; + } + + /** + * old abstract function not used by canvas renderer + * @deprecated since 6.4.0 + */ + get preserveDrawingBuffer(): boolean + { + // #if _DEBUG + deprecation('6.4.0', 'renderer.preserveDrawingBuffer has been deprecated'); + // #endif + + return false; + } + + /** + * old abstract function not used by canvas renderer + * @deprecated since 6.4.0 + */ + get useContextAlpha(): boolean + { + // #if _DEBUG + deprecation('6.4.0', 'renderer.useContextAlpha has been deprecated'); + // #endif + + return false; } static __plugins: IRendererPlugins; diff --git a/packages/canvas-renderer/src/Renderer.ts b/packages/canvas-renderer/src/Renderer.ts index f253f8713d..4f08ffb7a7 100644 --- a/packages/canvas-renderer/src/Renderer.ts +++ b/packages/canvas-renderer/src/Renderer.ts @@ -1,7 +1,7 @@ import { Renderer } from '@pixi/core'; import { CanvasRenderer } from './CanvasRenderer'; -import type { AbstractRenderer, IRendererOptionsAuto } from '@pixi/core'; +import type { IRenderer, IRendererOptionsAuto } from '@pixi/core'; // Reference to Renderer.create static function const parentCreate = Renderer.create; @@ -12,7 +12,7 @@ const parentCreate = Renderer.create; * @param options * @private */ -Renderer.create = function create(options: IRendererOptionsAuto): AbstractRenderer +Renderer.create = function create(options: IRendererOptionsAuto): IRenderer { const forceCanvas = options && options.forceCanvas; @@ -28,5 +28,6 @@ Renderer.create = function create(options: IRendererOptionsAuto): AbstractRender } } + // return parentCreate(options); return new CanvasRenderer(options); }; diff --git a/packages/canvas-renderer/src/index.ts b/packages/canvas-renderer/src/index.ts index 71bc5ee8fb..c322b9f715 100644 --- a/packages/canvas-renderer/src/index.ts +++ b/packages/canvas-renderer/src/index.ts @@ -1,4 +1,5 @@ export * from './CanvasRenderer'; +export * from './CanvasContextSystem'; export * from './utils/canUseNewCanvasBlendModes'; export * from './canvasUtils'; diff --git a/packages/canvas-renderer/test/CanvasMaskManager.tests.ts b/packages/canvas-renderer/test/CanvasMaskManager.tests.ts index 4d19825c11..1d54f40090 100644 --- a/packages/canvas-renderer/test/CanvasMaskManager.tests.ts +++ b/packages/canvas-renderer/test/CanvasMaskManager.tests.ts @@ -10,8 +10,8 @@ describe('CanvasMaskManager', () => it('should work on all graphics masks inside container', () => { const renderer = new CanvasRenderer({ width: 1, height: 1 }); - const shapeSpy = sinon.spy(renderer.maskManager, 'renderGraphicsShape'); - const contextPath = sinon.spy(renderer.context, 'closePath'); + const shapeSpy = sinon.spy(renderer.mask, 'renderGraphicsShape'); + const contextPath = sinon.spy(renderer.canvasContext.activeContext, 'closePath'); const cont = new Container(); cont.mask = new Sprite(); @@ -43,7 +43,7 @@ describe('CanvasMaskManager', () => it('should set correct transform for graphics', () => { const renderer = new CanvasRenderer({ width: 1, height: 1 }); - const transformSpy = sinon.spy(renderer.context, 'setTransform'); + const transformSpy = sinon.spy(renderer.canvasContext.activeContext, 'setTransform'); const cont = new Container(); const graphics1 = new Graphics(); const graphics2 = new Graphics(); diff --git a/packages/canvas-renderer/test/CanvasRenderer.tests.ts b/packages/canvas-renderer/test/CanvasRenderer.tests.ts index 1b20942b45..22c4342dbf 100644 --- a/packages/canvas-renderer/test/CanvasRenderer.tests.ts +++ b/packages/canvas-renderer/test/CanvasRenderer.tests.ts @@ -11,7 +11,7 @@ describe('CanvasRenderer', () => try { - expect(renderer.context).to.equal(renderer.rootContext); + expect(renderer.canvasContext.activeContext).to.equal(renderer.canvasContext.activeContext); } finally { diff --git a/packages/canvas-sprite-tiling/src/TilingSprite.ts b/packages/canvas-sprite-tiling/src/TilingSprite.ts index 0002201326..8ac830e028 100644 --- a/packages/canvas-sprite-tiling/src/TilingSprite.ts +++ b/packages/canvas-sprite-tiling/src/TilingSprite.ts @@ -25,7 +25,7 @@ TilingSprite.prototype._renderCanvas = function _renderCanvas(renderer: CanvasRe return; } - const context = renderer.context; + const context = renderer.canvasContext.activeContext; const transform = this.worldTransform; const baseTexture = texture.baseTexture; const source = baseTexture.getDrawableSource(); @@ -57,7 +57,7 @@ TilingSprite.prototype._renderCanvas = function _renderCanvas(renderer: CanvasRe // set context state.. context.globalAlpha = this.worldAlpha; - renderer.setBlendMode(this.blendMode); + renderer.canvasContext.setBlendMode(this.blendMode); this.tileTransform.updateLocalTransform(); const lt = this.tileTransform.localTransform; @@ -100,7 +100,7 @@ TilingSprite.prototype._renderCanvas = function _renderCanvas(renderer: CanvasRe * * Local Space (-localBounds.x, -localBounds.y) <--> Pattern Space (0, 0) * - * Here the mapping is provided by the tileTransfrom PLUS some "shift". This shift is done POST-tileTransform. The shift + * Here the mapping is provided by the tileTransform PLUS some "shift". This shift is done POST-tileTransform. The shift * is equal to the position of the top-left corner of the tiling sprite in its local space. * * Hence, @@ -110,14 +110,14 @@ TilingSprite.prototype._renderCanvas = function _renderCanvas(renderer: CanvasRe // worldMatrix is used to convert from pattern space to world space. // - // worldMatrix = tileTransform x shiftTransform x worldTransfrom + // worldMatrix = tileTransform x shiftTransform x worldTransform // = patternMatrix x worldTransform worldMatrix.identity(); // patternMatrix is used to convert from pattern space to local space. The drawing commands are issued in pattern space // and this matrix is used to inverse-map the local space vertices into it. // - // patternMatrix = tileTransfrom x shiftTransform + // patternMatrix = tileTransform x shiftTransform patternMatrix.copyFrom(lt); // Apply shiftTransform into patternMatrix. See $1.1 @@ -130,7 +130,7 @@ TilingSprite.prototype._renderCanvas = function _renderCanvas(renderer: CanvasRe worldMatrix.prepend(patternMatrix); worldMatrix.prepend(transform); - renderer.setContextTransform(worldMatrix); + renderer.canvasContext.setContextTransform(worldMatrix); // Fill the pattern! context.fillStyle = this._canvasPattern; diff --git a/packages/canvas-sprite/src/CanvasSpriteRenderer.ts b/packages/canvas-sprite/src/CanvasSpriteRenderer.ts index d9be1dcd46..7253b99a11 100644 --- a/packages/canvas-sprite/src/CanvasSpriteRenderer.ts +++ b/packages/canvas-sprite/src/CanvasSpriteRenderer.ts @@ -49,7 +49,8 @@ export class CanvasSpriteRenderer { const texture = sprite._texture; const renderer = this.renderer; - const context = renderer.context; + const context = renderer.canvasContext.activeContext; + const activeResolution = renderer.canvasContext.activeResolution; if (!texture.valid) { @@ -70,17 +71,18 @@ export class CanvasSpriteRenderer return; } - renderer.setBlendMode(sprite.blendMode, true); + renderer.canvasContext.setBlendMode(sprite.blendMode, true); - renderer.context.globalAlpha = sprite.worldAlpha; + context.globalAlpha = sprite.worldAlpha; // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture const smoothingEnabled = texture.baseTexture.scaleMode === SCALE_MODES.LINEAR; + const smoothProperty = renderer.canvasContext.smoothProperty; - if (renderer.smoothProperty - && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + if (smoothProperty + && context[smoothProperty] !== smoothingEnabled) { - context[renderer.smoothProperty] = smoothingEnabled; + context[smoothProperty] = smoothingEnabled; } if (texture.trim) @@ -107,7 +109,7 @@ export class CanvasSpriteRenderer dx -= width / 2; dy -= height / 2; - renderer.setContextTransform(wt, sprite.roundPixels, 1); + renderer.canvasContext.setContextTransform(wt, sprite.roundPixels, 1); // Allow for pixel rounding if (sprite.roundPixels) { @@ -116,17 +118,18 @@ export class CanvasSpriteRenderer } const resolution = texture.baseTexture.resolution; - const outerBlend = renderer._outerBlend; + + const outerBlend = renderer.canvasContext._outerBlend; if (outerBlend) { context.save(); context.beginPath(); context.rect( - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution + dx * activeResolution, + dy * activeResolution, + width * activeResolution, + height * activeResolution ); context.clip(); } @@ -147,10 +150,10 @@ export class CanvasSpriteRenderer 0, Math.floor(width * resolution), Math.floor(height * resolution), - Math.floor(dx * renderer.resolution), - Math.floor(dy * renderer.resolution), - Math.floor(width * renderer.resolution), - Math.floor(height * renderer.resolution) + Math.floor(dx * activeResolution), + Math.floor(dy * activeResolution), + Math.floor(width * activeResolution), + Math.floor(height * activeResolution) ); } else @@ -161,10 +164,10 @@ export class CanvasSpriteRenderer texture._frame.y * resolution, Math.floor(width * resolution), Math.floor(height * resolution), - Math.floor(dx * renderer.resolution), - Math.floor(dy * renderer.resolution), - Math.floor(width * renderer.resolution), - Math.floor(height * renderer.resolution) + Math.floor(dx * activeResolution), + Math.floor(dy * activeResolution), + Math.floor(width * activeResolution), + Math.floor(height * activeResolution) ); } @@ -173,7 +176,7 @@ export class CanvasSpriteRenderer context.restore(); } // just in case, leaking outer blend here will be catastrophic! - renderer.setBlendMode(BLEND_MODES.NORMAL); + renderer.canvasContext.setBlendMode(BLEND_MODES.NORMAL); } /** destroy the sprite object */ diff --git a/packages/core/src/AbstractRenderer.ts b/packages/core/src/AbstractRenderer.ts deleted file mode 100644 index c566d42879..0000000000 --- a/packages/core/src/AbstractRenderer.ts +++ /dev/null @@ -1,447 +0,0 @@ -import { hex2string, hex2rgb, EventEmitter, deprecation } from '@pixi/utils'; -import { Matrix, Rectangle, Transform } from '@pixi/math'; -import { MSAA_QUALITY, RENDERER_TYPE } from '@pixi/constants'; -import { settings } from '@pixi/settings'; -import { RenderTexture } from './renderTexture/RenderTexture'; - -import type { SCALE_MODES } from '@pixi/constants'; -import type { ISystemConstructor } from './ISystem'; -import type { IRenderingContext } from './IRenderingContext'; -import type { IRenderableContainer, IRenderableObject } from './IRenderableObject'; - -const tempMatrix = new Matrix(); -const tempTransform = new Transform(); - -export interface IRendererOptions extends GlobalMixins.IRendererOptions -{ - width?: number; - height?: number; - view?: HTMLCanvasElement; - useContextAlpha?: boolean | 'notMultiplied'; - /** - * Use `backgroundAlpha` instead. - * @deprecated - */ - transparent?: boolean; - autoDensity?: boolean; - antialias?: boolean; - resolution?: number; - preserveDrawingBuffer?: boolean; - clearBeforeRender?: boolean; - backgroundColor?: number; - backgroundAlpha?: number; - powerPreference?: WebGLPowerPreference; - context?: IRenderingContext; -} - -export interface IRendererPlugins -{ - [key: string]: any; -} - -export interface IRendererRenderOptions -{ - renderTexture?: RenderTexture; - clear?: boolean; - transform?: Matrix; - skipUpdateTransform?: boolean; -} - -export interface IGenerateTextureOptions -{ - scaleMode?: SCALE_MODES; - resolution?: number; - region?: Rectangle; - multisample?: MSAA_QUALITY; -} - -/** - * The AbstractRenderer is the base for a PixiJS Renderer. It is extended by the {@link PIXI.CanvasRenderer} - * and {@link PIXI.Renderer} which can be used for rendering a PixiJS scene. - * @abstract - * @class - * @extends PIXI.utils.EventEmitter - * @memberof PIXI - */ -export abstract class AbstractRenderer extends EventEmitter -{ - public resolution: number; - public clearBeforeRender?: boolean; - public readonly options: IRendererOptions; - public readonly type: RENDERER_TYPE; - public readonly screen: Rectangle; - public readonly view: HTMLCanvasElement; - public readonly plugins: IRendererPlugins; - public readonly useContextAlpha: boolean | 'notMultiplied'; - public readonly autoDensity: boolean; - public readonly preserveDrawingBuffer: boolean; - - protected _backgroundColor: number; - protected _backgroundColorString: string; - _backgroundColorRgba: number[]; - _lastObjectRendered: IRenderableObject; - - /** - * @param type - The renderer type. - * @param [options] - The optional renderer parameters. - * @param {number} [options.width=800] - The width of the screen. - * @param {number} [options.height=600] - The height of the screen. - * @param {HTMLCanvasElement} [options.view] - The canvas to use as a view, optional. - * @param {boolean} [options.useContextAlpha=true] - Pass-through value for canvas' context `alpha` property. - * If you want to set transparency, please use `backgroundAlpha`. This option is for cases where the - * canvas needs to be opaque, possibly for performance reasons on some older devices. - * @param {boolean} [options.autoDensity=false] - Resizes renderer view in CSS pixels to allow for - * resolutions other than 1. - * @param {boolean} [options.antialias=false] - Sets antialias - * @param {number} [options.resolution=PIXI.settings.RESOLUTION] - The resolution / device pixel ratio of the renderer. - * @param {boolean} [options.preserveDrawingBuffer=false] - Enables drawing buffer preservation, - * enable this if you need to call toDataUrl on the WebGL context. - * @param {boolean} [options.clearBeforeRender=true] - This sets if the renderer will clear the canvas or - * not before the new render pass. - * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area - * (shown if not transparent). - * @param {number} [options.backgroundAlpha=1] - Value from 0 (fully transparent) to 1 (fully opaque). - */ - constructor(type: RENDERER_TYPE = RENDERER_TYPE.UNKNOWN, options?: IRendererOptions) - { - super(); - - // Add the default render options - options = Object.assign({}, settings.RENDER_OPTIONS, options); - - /** - * The supplied constructor options. - * @member {object} - * @readonly - */ - this.options = options; - - /** - * The type of the renderer. - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE - */ - this.type = type; - - /** - * Measurements of the screen. (0, 0, screenWidth, screenHeight). - * - * Its safe to use as filterArea or hitArea for the whole stage. - * @member {PIXI.Rectangle} - */ - this.screen = new Rectangle(0, 0, options.width, options.height); - - /** - * The canvas element that everything is drawn to. - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); - - /** - * The resolution / device pixel ratio of the renderer. - * @member {number} - * @default PIXI.settings.RESOLUTION - */ - this.resolution = options.resolution || settings.RESOLUTION; - - /** - * Pass-thru setting for the canvas' context `alpha` property. This is typically - * not something you need to fiddle with. If you want transparency, use `backgroundAlpha`. - * @member {boolean} - */ - this.useContextAlpha = options.useContextAlpha; - - /** - * Whether CSS dimensions of canvas view should be resized to screen dimensions automatically. - * @member {boolean} - */ - this.autoDensity = !!options.autoDensity; - - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of - * the stencil buffer is retained after rendering. - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; - - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent PixiJS will use a canvas sized fillRect operation every - * frame to set the canvas background color. If the scene is transparent PixiJS will use clearRect - * to clear the canvas every frame. Disable this by setting this to false. For example, if - * your game has a canvas filling background image you often don't need this set. - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; - - /** - * The background color as a number. - * @member {number} - * @protected - */ - this._backgroundColor = 0x000000; - - /** - * The background color as an [R, G, B, A] array. - * @member {number[]} - * @protected - */ - this._backgroundColorRgba = [0, 0, 0, 1]; - - /** - * The background color as a string. - * @member {string} - * @protected - */ - this._backgroundColorString = '#000000'; - - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - this.backgroundAlpha = options.backgroundAlpha; - - // @deprecated - if (options.transparent !== undefined) - { - // #if _DEBUG - deprecation('6.0.0', 'Option transparent is deprecated, please use backgroundAlpha instead.'); - // #endif - this.useContextAlpha = options.transparent; - this.backgroundAlpha = options.transparent ? 0 : 1; - } - - /** - * The last root object that the renderer tried to render. - * @member {PIXI.DisplayObject} - * @protected - */ - this._lastObjectRendered = null; - - /** - * Collection of plugins. - * @readonly - * @member {object} - */ - this.plugins = {}; - } - - /** - * Initialize the plugins. - * @protected - * @param {object} staticMap - The dictionary of statically saved plugins. - */ - initPlugins(staticMap: IRendererPlugins): void - { - for (const o in staticMap) - { - this.plugins[o] = new (staticMap[o])(this); - } - } - - /** - * Same as view.width, actual number of pixels in the canvas by horizontal. - * @member {number} - * @readonly - * @default 800 - */ - get width(): number - { - return this.view.width; - } - - /** - * Same as view.height, actual number of pixels in the canvas by vertical. - * @member {number} - * @readonly - * @default 600 - */ - get height(): number - { - return this.view.height; - } - - /** - * Resizes the screen and canvas as close as possible to the specified width and height. - * Canvas dimensions are multiplied by resolution and rounded to the nearest integers. - * The new canvas dimensions divided by the resolution become the new screen dimensions. - * @param desiredScreenWidth - The desired width of the screen. - * @param desiredScreenHeight - The desired height of the screen. - */ - resize(desiredScreenWidth: number, desiredScreenHeight: number): void - { - this.view.width = Math.round(desiredScreenWidth * this.resolution); - this.view.height = Math.round(desiredScreenHeight * this.resolution); - - const screenWidth = this.view.width / this.resolution; - const screenHeight = this.view.height / this.resolution; - - this.screen.width = screenWidth; - this.screen.height = screenHeight; - - if (this.autoDensity) - { - this.view.style.width = `${screenWidth}px`; - this.view.style.height = `${screenHeight}px`; - } - - /** - * Fired after view has been resized. - * @event PIXI.Renderer#resize - * @param {number} screenWidth - The new width of the screen. - * @param {number} screenHeight - The new height of the screen. - */ - this.emit('resize', screenWidth, screenHeight); - } - - /** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * @method PIXI.AbstractRenderer#generateTexture - * @param displayObject - The displayObject the object will be generated from. - * @param {object} options - Generate texture options. - * @param {PIXI.SCALE_MODES} options.scaleMode - The scale mode of the texture. - * @param {number} options.resolution - The resolution / device pixel ratio of the texture being generated. - * @param {PIXI.Rectangle} options.region - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @param {PIXI.MSAA_QUALITY} options.multisample - The number of samples of the frame buffer. - * @returns A texture of the graphics object. - */ - generateTexture(displayObject: IRenderableObject, options?: IGenerateTextureOptions): RenderTexture; - - /** - * Please use the options argument instead. - * @method PIXI.AbstractRenderer#generateTexture - * @deprecated Since 6.1.0 - * @param displayObject - The displayObject the object will be generated from. - * @param scaleMode - The scale mode of the texture. - * @param resolution - The resolution / device pixel ratio of the texture being generated. - * @param region - The region of the displayObject, that shall be rendered, - * if no region is specified, defaults to the local bounds of the displayObject. - * @returns A texture of the graphics object. - */ - generateTexture( - displayObject: IRenderableObject, - scaleMode?: SCALE_MODES, - resolution?: number, - region?: Rectangle): RenderTexture; - - /** - * @ignore - */ - generateTexture(displayObject: IRenderableObject, - options: IGenerateTextureOptions | SCALE_MODES = {}, - resolution?: number, region?: Rectangle): RenderTexture - { - // @deprecated parameters spread, use options instead - if (typeof options === 'number') - { - // #if _DEBUG - deprecation('6.1.0', 'generateTexture options (scaleMode, resolution, region) are now object options.'); - // #endif - - options = { scaleMode: options, resolution, region }; - } - - const { region: manualRegion, ...textureOptions } = options; - - region = manualRegion || (displayObject as IRenderableContainer).getLocalBounds(null, true); - - // minimum texture size is 1x1, 0x0 will throw an error - if (region.width === 0) region.width = 1; - if (region.height === 0) region.height = 1; - - const renderTexture = RenderTexture.create( - { - width: region.width, - height: region.height, - ...textureOptions, - }); - - tempMatrix.tx = -region.x; - tempMatrix.ty = -region.y; - - const transform = displayObject.transform; - - displayObject.transform = tempTransform; - - this.render(displayObject, { - renderTexture, - transform: tempMatrix - }); - - displayObject.transform = transform; - - return renderTexture; - } - - /** - * Adds a new system to the renderer. - * @param ClassRef - Class reference - * @param name - Property name for system - * @returns Return instance of renderer - */ - abstract addSystem(ClassRef: ISystemConstructor, name: string): this; - - abstract render(displayObject: IRenderableObject, options?: IRendererRenderOptions): void; - - /** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * @param [removeView=false] - Removes the Canvas element from the DOM. - */ - destroy(removeView?: boolean): void - { - for (const o in this.plugins) - { - this.plugins[o].destroy(); - this.plugins[o] = null; - } - - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - const thisAny = this as any; - - // null-ing all objects, that's a tradition! - - thisAny.plugins = null; - thisAny.type = RENDERER_TYPE.UNKNOWN; - thisAny.view = null; - thisAny.screen = null; - thisAny._tempDisplayObjectParent = null; - thisAny.options = null; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - this._lastObjectRendered = null; - } - - /** - * The background color to fill if not transparent - * @member {number} - */ - get backgroundColor(): number - { - return this._backgroundColor; - } - - set backgroundColor(value: number) - { - this._backgroundColor = value; - this._backgroundColorString = hex2string(value); - hex2rgb(value, this._backgroundColorRgba); - } - - /** - * The background color alpha. Setting this to 0 will make the canvas transparent. - * @member {number} - */ - get backgroundAlpha(): number - { - return this._backgroundColorRgba[3]; - } - set backgroundAlpha(value: number) - { - this._backgroundColorRgba[3] = value; - } -} diff --git a/packages/core/src/IRenderableObject.ts b/packages/core/src/IRenderableObject.ts deleted file mode 100644 index 94d4442d24..0000000000 --- a/packages/core/src/IRenderableObject.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { Rectangle, Transform } from '@pixi/math'; -import type { Renderer } from './Renderer'; - -/** - * Interface for DisplayObject to interface with Renderer. - * The minimum APIs needed to implement a renderable object. - * @memberof PIXI - */ -interface IRenderableObject -{ - /** Object must have a parent container */ - parent: IRenderableContainer; - /** Object must have a transform */ - transform: Transform; - /** Before method for transform updates */ - enableTempParent(): IRenderableContainer; - /** Update the transforms */ - updateTransform(): void; - /** After method for transform updates */ - disableTempParent(parent: IRenderableContainer): void; - /** Render object directly */ - render(renderer: Renderer): void; -} - -/** - * Interface for Container to interface with Renderer. - * @memberof PIXI - */ -interface IRenderableContainer extends IRenderableObject -{ - /** Get Local bounds for container */ - getLocalBounds(rect?: Rectangle, skipChildrenUpdate?: boolean): Rectangle; -} - -export type { IRenderableObject, IRenderableContainer }; diff --git a/packages/core/src/IRenderer.ts b/packages/core/src/IRenderer.ts new file mode 100644 index 0000000000..0192157470 --- /dev/null +++ b/packages/core/src/IRenderer.ts @@ -0,0 +1,124 @@ +import type { RENDERER_TYPE } from '@pixi/constants'; +import type { Matrix, Rectangle, Transform } from '@pixi/math'; +import type { IGenerateTextureOptions } from './renderTexture/GenerateTextureSystem'; +import type { IRendererPlugins } from './plugin/PluginSystem'; +import type { RenderTexture } from './renderTexture/RenderTexture'; +import type { SystemManager } from './system/SystemManager'; + +/** + * Interface for DisplayObject to interface with Renderer. + * The minimum APIs needed to implement a renderable object. + * @memberof PIXI + */ +export interface IRenderableObject +{ + /** Object must have a parent container */ + parent: IRenderableContainer; + /** Object must have a transform */ + transform: Transform; + /** Before method for transform updates */ + enableTempParent(): IRenderableContainer; + /** Update the transforms */ + updateTransform(): void; + /** After method for transform updates */ + disableTempParent(parent: IRenderableContainer): void; + /** Render object directly */ + render(renderer: IRenderer): void; +} + +/** + * Interface for Container to interface with Renderer. + * @memberof PIXI + */ +export interface IRenderableContainer extends IRenderableObject +{ + /** Get Local bounds for container */ + getLocalBounds(rect?: Rectangle, skipChildrenUpdate?: boolean): Rectangle; +} + +/** Mixed WebGL1/WebGL2 Rendering Context. Either its WebGL2, either its WebGL1 with PixiJS polyfills on it */ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface IRenderingContext extends WebGL2RenderingContext +{ + +} + +export interface IRendererOptions extends GlobalMixins.IRendererOptions +{ + width?: number; + height?: number; + view?: HTMLCanvasElement; + /** + * Use premultipliedAlpha and backgroundAlpha instead + * @deprecated + */ + useContextAlpha?: boolean | 'notMultiplied'; + /** + * Use `backgroundAlpha` instead. + * @deprecated + */ + transparent?: boolean; + autoDensity?: boolean; + antialias?: boolean; + resolution?: number; + preserveDrawingBuffer?: boolean; + clearBeforeRender?: boolean; + backgroundColor?: number; + backgroundAlpha?: number; + premultipliedAlpha?: boolean; + powerPreference?: WebGLPowerPreference; + context?: IRenderingContext; +} + +export interface IRendererRenderOptions +{ + renderTexture?: RenderTexture; + blit?: boolean + clear?: boolean; + transform?: Matrix; + skipUpdateTransform?: boolean; +} + +/** + * Starard Interface for a Pixi renderer. + * @memberof PIXI + */ +export interface IRenderer extends SystemManager +{ + + resize(width: number, height: number): void; + render(displayObject: IRenderableObject, options?: IRendererRenderOptions): void + generateTexture(displayObject: IRenderableObject, options?: IGenerateTextureOptions): void + destroy(removeView?: boolean): void; + clear(): void; + reset(): void; + + /** + * The type of the renderer. + * @see PIXI.RENDERER_TYPE + */ + readonly type: RENDERER_TYPE + + /** When logging Pixi to the console, this is the name we will show */ + readonly rendererLogId: string + + /** The canvas element that everything is drawn to.*/ + readonly view: HTMLCanvasElement + /** Flag if we are rendering to the screen vs renderTexture */ + readonly renderingToScreen: boolean + /** The resolution / device pixel ratio of the renderer. */ + readonly resolution: number + /** the width of the screen */ + readonly width: number + /** the height of the screen */ + readonly height: number + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight). + * Its safe to use as filterArea or hitArea for the whole stage. + */ + readonly screen: Rectangle + /** the last object rendered by the renderer. Useful for other plugins like interaction managers */ + readonly lastObjectRendered: IRenderableObject + /** Collection of plugins */ + readonly plugins: IRendererPlugins +} diff --git a/packages/core/src/IRenderingContext.ts b/packages/core/src/IRenderingContext.ts deleted file mode 100644 index da81f3584d..0000000000 --- a/packages/core/src/IRenderingContext.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** Mixed WebGL1/WebGL2 Rendering Context. Either its WebGL2, either its WebGL1 with PixiJS polyfills on it */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface IRenderingContext extends WebGL2RenderingContext -{ - -} diff --git a/packages/core/src/Renderer.ts b/packages/core/src/Renderer.ts index 66e505861f..d87b5b5713 100644 --- a/packages/core/src/Renderer.ts +++ b/packages/core/src/Renderer.ts @@ -1,5 +1,4 @@ -import { AbstractRenderer } from './AbstractRenderer'; -import { sayHello, isWebGLSupported, deprecation } from '@pixi/utils'; +import { isWebGLSupported, deprecation } from '@pixi/utils'; import { MaskSystem } from './mask/MaskSystem'; import { StencilSystem } from './mask/StencilSystem'; import { ScissorSystem } from './mask/ScissorSystem'; @@ -17,33 +16,28 @@ import { TextureGCSystem } from './textures/TextureGCSystem'; import { MSAA_QUALITY, RENDERER_TYPE } from '@pixi/constants'; import { UniformGroup } from './shader/UniformGroup'; import { Matrix, Rectangle } from '@pixi/math'; -import { Runner } from '@pixi/runner'; import { BufferSystem } from './geometry/BufferSystem'; import { RenderTexture } from './renderTexture/RenderTexture'; import type { SCALE_MODES } from '@pixi/constants'; -import type { IRendererOptions, IRendererPlugins, IRendererRenderOptions, - IGenerateTextureOptions } from './AbstractRenderer'; -import type { ISystemConstructor } from './ISystem'; -import type { IRenderingContext } from './IRenderingContext'; -import type { IRenderableObject } from './IRenderableObject'; -export interface IRendererPluginConstructor -{ - new (renderer: Renderer, options?: any): IRendererPlugin; -} - -export interface IRendererPlugin -{ - destroy(): void; -} +import { IRendererPluginConstructor, IRendererPlugins, PluginSystem } from './plugin/PluginSystem'; +import { MultisampleSystem } from './framebuffer/MultisampleSystem'; +import { GenerateTextureSystem, IGenerateTextureOptions } from './renderTexture/GenerateTextureSystem'; +import { BackgroundSystem } from './background/BackgroundSystem'; +import { ViewSystem } from './view/ViewSystem'; +import { ObjectRendererSystem } from './render/ObjectRendererSystem'; +import { settings } from '@pixi/settings'; +import { SystemManager } from './system/SystemManager'; +import { IRenderableObject, IRenderer, IRendererOptions, IRendererRenderOptions, IRenderingContext } from './IRenderer'; +import { StartupOptions, StartupSystem } from './startup/StartupSystem'; /** * The Renderer draws the scene and all its content onto a WebGL enabled canvas. * * This renderer should be used for browsers that support WebGL. * - * This renderer works by automatically managing WebGLBatchesm, so no need for Sprite Batches or Sprite Clouds. + * This renderer works by automatically managing WebGLBatches, so no need for Sprite Batches or Sprite Clouds. * Don't forget to add the view to your DOM or you will not see anything! * * Renderer is composed of systems that manage specific tasks. The following systems are added by default @@ -51,27 +45,50 @@ export interface IRendererPlugin * * | System | Description | * | ------------------------------------ | ----------------------------------------------------------------------------- | - * | {@link PIXI.BatchSystem} | This manages object renderers that defer rendering until a flush. | - * | {@link PIXI.ContextSystem} | This manages the WebGL context and extensions. | + * + * | Generic Systems | Systems that manage functionality that all renderer types share | + * | ------------------------------------ | ----------------------------------------------------------------------------- | + * | {@link PIXI.ViewSystem} | This manages the main view of the renderer usually a Canvas | + * | {@link PIXI.PluginSystem} | This manages plugins for the renderer | + * | {@link PIXI.BackgroundSystem} | This manages the main views background color and alpha | + * | {@link PIXI.StartupSystem} | Boots up a renderer and initiatives all the systems | * | {@link PIXI.EventSystem} | This manages UI events. | - * | {@link PIXI.FilterSystem} | This manages the filtering pipeline for post-processing effects. | + * + * | WebGL Core Systems | Provide an optimised, easy to use API to work with WebGL | + * | ------------------------------------ | ----------------------------------------------------------------------------- | + * | {@link PIXI.ContextSystem} | This manages the WebGL context and extensions. | * | {@link PIXI.FramebufferSystem} | This manages framebuffers, which are used for offscreen rendering. | * | {@link PIXI.GeometrySystem} | This manages geometries & buffers, which are used to draw object meshes. | - * | {@link PIXI.MaskSystem} | This manages masking operations. | - * | {@link PIXI.ProjectionSystem} | This manages the `projectionMatrix`, used by shaders to get NDC coordinates. | - * | {@link PIXI.RenderTextureSystem} | This manages render-textures, which are an abstraction over framebuffers. | - * | {@link PIXI.ScissorSystem} | This handles scissor masking, and is used internally by {@link MaskSystem} | * | {@link PIXI.ShaderSystem} | This manages shaders, programs that run on the GPU to calculate 'em pixels. | * | {@link PIXI.StateSystem} | This manages the WebGL state variables like blend mode, depth testing, etc. | - * | {@link PIXI.StencilSystem} | This handles stencil masking, and is used internally by {@link MaskSystem} | * | {@link PIXI.TextureSystem} | This manages textures and their resources on the GPU. | * | {@link PIXI.TextureGCSystem} | This will automatically remove textures from the GPU if they are not used. | + * | {@link PIXI.MultisampleSystem} | This manages the multisample const on the WEbGL Renderer | + * + * | Pixi high level Systems | Set of Pixi specific systems designed to work with Pixi objects | + * | ------------------------------------ | ----------------------------------------------------------------------------- | + * | {@link PIXI.RenderSystem} | This adds the ability to render a PIXI.DisplayObject | + * | {@link PIXI.GenerateTextureSystem} | This adds the ability to generate textures from any PIXI.DisplayObject | + * | {@link PIXI.ProjectionSystem} | This manages the `projectionMatrix`, used by shaders to get NDC coordinates. | + * | {@link PIXI.RenderTextureSystem} | This manages render-textures, which are an abstraction over framebuffers. | + * | {@link PIXI.MaskSystem} | This manages masking operations. | + * | {@link PIXI.ScissorSystem} | This handles scissor masking, and is used internally by {@link MaskSystem} | + * | {@link PIXI.StencilSystem} | This handles stencil masking, and is used internally by {@link MaskSystem} | + * | {@link PIXI.FilterSystem} | This manages the filtering pipeline for post-processing effects. | + * | {@link PIXI.BatchSystem} | This manages object renderers that defer rendering until a flush. | * * The breadth of the API surface provided by the renderer is contained within these systems. * @memberof PIXI */ -export class Renderer extends AbstractRenderer +export class Renderer extends SystemManager implements IRenderer { + /** + * The type of the renderer. will be PIXI.RENDERER_TYPE.CANVAS + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + public readonly type: RENDERER_TYPE.WEBGL; + /** * WebGL context, set by {@link PIXI.ContextSystem this.context}. * @readonly @@ -79,115 +96,151 @@ export class Renderer extends AbstractRenderer */ public gl: IRenderingContext; - /** Global uniforms */ + /** + * Global uniforms + * Add any uniforms you want shared across your shaders. + * the must be added before the scene is rendered for the first time + * as we dynamically buildcode to handle all global var per shader + * + */ public globalUniforms: UniformGroup; /** Unique UID assigned to the renderer's WebGL context. */ public CONTEXT_UID: number; - /** - * Flag if we are rendering to the screen vs renderTexture - * @readonly - * @default true - */ - public renderingToScreen: boolean; - - /** - * The number of msaa samples of the canvas. - * @readonly - */ - public multisample: MSAA_QUALITY; // systems /** * Mask system instance * @readonly */ - public mask: MaskSystem; + public readonly mask: MaskSystem; /** * Context system instance * @readonly */ - public context: ContextSystem; + public readonly context: ContextSystem; /** * State system instance * @readonly */ - public state: StateSystem; + public readonly state: StateSystem; /** * Shader system instance * @readonly */ - public shader: ShaderSystem; + public readonly shader: ShaderSystem; /** * Texture system instance * @readonly */ - public texture: TextureSystem; + public readonly texture: TextureSystem; /** * Buffer system instance * @readonly */ - public buffer: BufferSystem; + public readonly buffer: BufferSystem; /** * Geometry system instance * @readonly */ - public geometry: GeometrySystem; + public readonly geometry: GeometrySystem; /** * Framebuffer system instance * @readonly */ - public framebuffer: FramebufferSystem; + public readonly framebuffer: FramebufferSystem; /** * Scissor system instance * @readonly */ - public scissor: ScissorSystem; + public readonly scissor: ScissorSystem; /** * Stencil system instance * @readonly */ - public stencil: StencilSystem; + public readonly stencil: StencilSystem; /** * Projection system instance * @readonly */ - public projection: ProjectionSystem; + public readonly projection: ProjectionSystem; /** * Texture garbage collector system instance * @readonly */ - public textureGC: TextureGCSystem; + public readonly textureGC: TextureGCSystem; /** * Filter system instance * @readonly */ - public filter: FilterSystem; + public readonly filter: FilterSystem; /** * RenderTexture system instance * @readonly */ - public renderTexture: RenderTextureSystem; + public readonly renderTexture: RenderTextureSystem; /** * Batch system instance * @readonly */ - public batch: BatchSystem; + public readonly batch: BatchSystem; + + /** + * plugin system instance + * @readonly + */ + public readonly _plugin: PluginSystem; + + /** + * _multisample system instance + * @readonly + */ + public readonly _multisample: MultisampleSystem; + + /** + * textureGenerator system instance + * @readonly + */ + public readonly textureGenerator: GenerateTextureSystem; + + /** + * background system instance + * @readonly + */ + public readonly background: BackgroundSystem; + + /** + * _view system instance + * @readonly + */ + public readonly _view: ViewSystem; + + /** + * _render system instance + * @readonly + */ + public readonly objectRenderer: ObjectRendererSystem; + + /** + * startup system instance + * @readonly + */ + public readonly startup: StartupSystem; /** * Internal signal instances of **runner**, these @@ -205,7 +258,6 @@ export class Renderer extends AbstractRenderer * @property {PIXI.Runner} prerender - Pre-render runner * @property {PIXI.Runner} resize - Resize runner */ - runners: {[key: string]: Runner}; /** * Create renderer if WebGL is available. Overrideable @@ -214,7 +266,7 @@ export class Renderer extends AbstractRenderer * @param options * @private */ - static create(options?: IRendererOptions): AbstractRenderer + static create(options?: IRendererOptions): IRenderer { if (isWebGLSupported()) { @@ -248,167 +300,86 @@ export class Renderer extends AbstractRenderer * @param {string} [options.powerPreference] - Parameter passed to WebGL context, set to "high-performance" * for devices with dual graphics card. * @param {object} [options.context] - If WebGL context already exists, all parameters must be taken from it. + * @param {object} [options.blit] - if rendering to a renderTexture, set to true if you want to run blit after + * the render. defaults to false. */ constructor(options?: IRendererOptions) { - super(RENDERER_TYPE.WEBGL, options); + super(); - // the options will have been modified here in the super constructor with pixi's default settings.. - options = this.options; + // Add the default render options + options = Object.assign({}, settings.RENDER_OPTIONS, options); this.gl = null; this.CONTEXT_UID = 0; - this.runners = { - destroy: new Runner('destroy'), - contextChange: new Runner('contextChange'), - reset: new Runner('reset'), - update: new Runner('update'), - postrender: new Runner('postrender'), - prerender: new Runner('prerender'), - resize: new Runner('resize'), - }; - - this.runners.contextChange.add(this); - this.globalUniforms = new UniformGroup({ projectionMatrix: new Matrix(), }, true); - this.addSystem(MaskSystem, 'mask') - .addSystem(ContextSystem, 'context') - .addSystem(StateSystem, 'state') - .addSystem(ShaderSystem, 'shader') - .addSystem(TextureSystem, 'texture') - .addSystem(BufferSystem, 'buffer') - .addSystem(GeometrySystem, 'geometry') - .addSystem(FramebufferSystem, 'framebuffer') - .addSystem(ScissorSystem, 'scissor') - .addSystem(StencilSystem, 'stencil') - .addSystem(ProjectionSystem, 'projection') - .addSystem(TextureGCSystem, 'textureGC') - .addSystem(FilterSystem, 'filter') - .addSystem(RenderTextureSystem, 'renderTexture') - .addSystem(BatchSystem, 'batch'); - - this.initPlugins(Renderer.__plugins); - - this.multisample = undefined; - - /* - * The options passed in to create a new WebGL context. - */ - if (options.context) - { - this.context.initFromContext(options.context); - } - else - { - this.context.initFromOptions({ - alpha: !!this.useContextAlpha, + const systemConfig = { + runners: ['init', 'destroy', 'contextChange', 'reset', 'update', 'postrender', 'prerender', 'resize'], + systems: { + // systems shared by all renderers.. + textureGenerator: GenerateTextureSystem, + background: BackgroundSystem, + _view: ViewSystem, + _plugin: PluginSystem, + startup: StartupSystem, + + // low level WebGL systems + context: ContextSystem, + state: StateSystem, + shader: ShaderSystem, + texture: TextureSystem, + buffer: BufferSystem, + geometry: GeometrySystem, + framebuffer: FramebufferSystem, + + // high level pixi specific rendering + mask: MaskSystem, + scissor: ScissorSystem, + stencil: StencilSystem, + projection: ProjectionSystem, + textureGC: TextureGCSystem, + filter: FilterSystem, + renderTexture: RenderTextureSystem, + batch: BatchSystem, + objectRenderer: ObjectRendererSystem, + _multisample: MultisampleSystem, + } + }; + + this.setup(systemConfig); + + // new options! + const startupOptions: StartupOptions = { + _plugin: Renderer.__plugins, + background: { + alpha: options.backgroundAlpha, + color: options.backgroundColor, + clearBeforeRender: options.clearBeforeRender, + transparent: options.transparent, + }, + _view: { + height: options.height, + width: options.width, + autoDensity: options.autoDensity, + resolution: options.resolution, + view: options.view, + }, + context: { antialias: options.antialias, - premultipliedAlpha: this.useContextAlpha && this.useContextAlpha !== 'notMultiplied', - stencil: true, + context: options.context, + powerPreference: options.powerPreference, + premultipliedAlpha: options.premultipliedAlpha + ?? (options.useContextAlpha && options.useContextAlpha !== 'notMultiplied'), preserveDrawingBuffer: options.preserveDrawingBuffer, - powerPreference: this.options.powerPreference, - }); - } - - this.renderingToScreen = true; - - sayHello(this.context.webGLVersion === 2 ? 'WebGL 2' : 'WebGL 1'); - - this.resize(this.options.width, this.options.height); - } - - protected contextChange(): void - { - const gl = this.gl; - - let samples; - - if (this.context.webGLVersion === 1) - { - const framebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); - - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - samples = gl.getParameter(gl.SAMPLES); - - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); - } - else - { - const framebuffer = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING); - - gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null); - - samples = gl.getParameter(gl.SAMPLES); - - gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, framebuffer); - } - - if (samples >= MSAA_QUALITY.HIGH) - { - this.multisample = MSAA_QUALITY.HIGH; - } - else if (samples >= MSAA_QUALITY.MEDIUM) - { - this.multisample = MSAA_QUALITY.MEDIUM; - } - else if (samples >= MSAA_QUALITY.LOW) - { - this.multisample = MSAA_QUALITY.LOW; - } - else - { - this.multisample = MSAA_QUALITY.NONE; - } - } - - /** - * Add a new system to the renderer. - * @param ClassRef - Class reference - * @param name - Property name for system, if not specified - * will use a static `name` property on the class itself. This - * name will be assigned as s property on the Renderer so make - * sure it doesn't collide with properties on Renderer. - * @returns Return instance of renderer - */ - addSystem(ClassRef: ISystemConstructor, name: string): this - { - const system = new ClassRef(this); - - if ((this as any)[name]) - { - throw new Error(`Whoops! The name "${name}" is already in use`); - } - - (this as any)[name] = system; - - for (const i in this.runners) - { - this.runners[i].add(system); - } - - /** - * Fired after rendering finishes. - * @event PIXI.Renderer#postrender - */ - - /** - * Fired before rendering starts. - * @event PIXI.Renderer#prerender - */ - - /** - * Fired when the WebGL context is set. - * @event PIXI.Renderer#context - * @param {WebGLRenderingContext} gl - WebGL context. - */ + }, + }; - return this; + this.startup.run(startupOptions); } /** @@ -439,104 +410,23 @@ export class Renderer extends AbstractRenderer */ render(displayObject: IRenderableObject, options?: IRendererRenderOptions | RenderTexture): void { - let renderTexture: RenderTexture; - let clear: boolean; - let transform: Matrix; - let skipUpdateTransform: boolean; - - if (options) + if (options instanceof RenderTexture) { - if (options instanceof RenderTexture) - { - // #if _DEBUG - deprecation('6.0.0', 'Renderer#render arguments changed, use options instead.'); - // #endif - - /* eslint-disable prefer-rest-params */ - renderTexture = options; - clear = arguments[2]; - transform = arguments[3]; - skipUpdateTransform = arguments[4]; - /* eslint-enable prefer-rest-params */ - } - else - { - renderTexture = options.renderTexture; - clear = options.clear; - transform = options.transform; - skipUpdateTransform = options.skipUpdateTransform; - } + // #if _DEBUG + deprecation('6.0.0', 'Renderer#render arguments changed, use options instead.'); + // #endif + + /* eslint-disable prefer-rest-params */ + options = { + renderTexture: options, + clear: arguments[2], + transform: arguments[3], + skipUpdateTransform: arguments[4] + }; + /* eslint-enable prefer-rest-params */ } - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.runners.prerender.emit(); - this.emit('prerender'); - - // apply a transform at a GPU level - this.projection.transform = transform; - - // no point rendering if our context has been blown up! - if (this.context.isLost) - { - return; - } - - if (!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if (!skipUpdateTransform) - { - // update the scene graph - const cacheParent = displayObject.enableTempParent(); - - displayObject.updateTransform(); - displayObject.disableTempParent(cacheParent); - // displayObject.hitArea = //TODO add a temp hit area - } - - this.renderTexture.bind(renderTexture); - this.batch.currentRenderer.start(); - - if (clear !== undefined ? clear : this.clearBeforeRender) - { - this.renderTexture.clear(); - } - - displayObject.render(this); - - // apply transform.. - this.batch.currentRenderer.flush(); - - if (renderTexture) - { - renderTexture.baseTexture.update(); - } - - this.runners.postrender.emit(); - - // reset transform after render - this.projection.transform = null; - - this.emit('postrender'); - } - - /** - * @override - * @ignore - */ - generateTexture(displayObject: IRenderableObject, - options: IGenerateTextureOptions | SCALE_MODES = {}, - resolution?: number, region?: Rectangle): RenderTexture - { - const renderTexture = super.generateTexture(displayObject, options as any, resolution, region); - - this.framebuffer.blit(); - - return renderTexture; + this.objectRenderer.render(displayObject, options); } /** @@ -546,9 +436,7 @@ export class Renderer extends AbstractRenderer */ resize(desiredScreenWidth: number, desiredScreenHeight: number): void { - super.resize(desiredScreenWidth, desiredScreenHeight); - - this.runners.resize.emit(this.screen.height, this.screen.width); + this._view.resizeView(desiredScreenWidth, desiredScreenHeight); } /** @@ -574,20 +462,15 @@ export class Renderer extends AbstractRenderer * @param [removeView=false] - Removes the Canvas element from the DOM. * See: https://github.com/pixijs/pixi.js/issues/2233 */ - destroy(removeView?: boolean): void + destroy(removeView = false): void { - this.runners.destroy.emit(); - - for (const r in this.runners) - { - this.runners[r].destroy(); - } + this.runners.destroy.items.reverse(); - // call base destroy - super.destroy(removeView); + this.emitWithCustomOptions(this.runners.destroy, { + _view: removeView, + }); - // TODO nullify all the managers.. - this.gl = null; + super.destroy(); } /** @@ -605,6 +488,235 @@ export class Renderer extends AbstractRenderer return this.plugins.extract; } + /** Collection of plugins */ + get plugins(): IRendererPlugins + { + return this._plugin.plugins; + } + + /** The number of msaa samples of the canvas. */ + get multisample(): MSAA_QUALITY + { + return this._multisample.multisample; + } + + /** + * Same as view.width, actual number of pixels in the canvas by horizontal. + * @member {number} + * @readonly + * @default 800 + */ + get width(): number + { + return this._view.element.width; + } + + /** + * Same as view.height, actual number of pixels in the canvas by vertical. + * @default 600 + */ + get height(): number + { + return this._view.element.height; + } + + /** The resolution / device pixel ratio of the renderer. */ + get resolution(): number + { + return this._view.resolution; + } + + /** Whether CSS dimensions of canvas view should be resized to screen dimensions automatically. */ + get autoDensity(): boolean + { + return this._view.autoDensity; + } + + /** The canvas element that everything is drawn to.*/ + get view(): HTMLCanvasElement + { + return this._view.element; + } + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight). + * + * Its safe to use as filterArea or hitArea for the whole stage. + * @member {PIXI.Rectangle} + */ + get screen(): Rectangle + { + return this._view.screen; + } + + /** the last object rendered by the renderer. Useful for other plugins like interaction managers */ + get lastObjectRendered(): IRenderableObject + { + return this.objectRenderer.lastObjectRendered; + } + + /** Flag if we are rendering to the screen vs renderTexture */ + get renderingToScreen(): boolean + { + return this.objectRenderer.renderingToScreen; + } + + /** When logging Pixi to the console, this is the name we will show */ + get rendererLogId(): string + { + return `WebGL ${this.context.webGLVersion}`; + } + + /** this sets weather the screen is totally cleared between each frame withthe background color and alpha */ + get clearBeforeRender(): boolean + { + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.useContextAlpha has been deprecated, please use renderer.background.clearBeforeRender instead.'); + // #endif + + return this.background.clearBeforeRender; + } + + /** + * Pass-thru setting for the canvas' context `alpha` property. This is typically + * not something you need to fiddle with. If you want transparency, use `backgroundAlpha`. + * @member {boolean} + */ + get useContextAlpha(): boolean | 'notMultiplied' + { + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'Renderer#useContextAlpha has been deprecated, please use Renderer#context.premultipliedAlpha instead.'); + // #endif + + return this.context.useContextAlpha; + } + + /** + * readonly drawing buffer preservation + * we can only know this if Pixi created the context + * @deprecated since 6.4.0 + */ + get preserveDrawingBuffer(): boolean + { + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.preserveDrawingBuffer has been deprecated, we cannot truly know this unless pixi created the context'); + // #endif + + return this.context.preserveDrawingBuffer; + } + + /** + * The background color to fill if not transparent + * @member {number} + * @deprecated since 6.4.0 + */ + get backgroundColor(): number + { + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.backgroundColor has been deprecated, use renderer.background.color instead.'); + // #endif + + return this.background.color; + } + + set backgroundColor(value: number) + { + // #if _DEBUG + deprecation('6.4.0', 'renderer.backgroundColor has been deprecated, use renderer.background.color instead.'); + // #endif + + this.background.color = value; + } + + /** + * The background color alpha. Setting this to 0 will make the canvas transparent. + * @member {number} + * @deprecated since 6.4.0 + */ + get backgroundAlpha(): number + { + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.backgroundAlpha has been deprecated, use renderer.background.alpha instead.'); + // #endif + + return this.background.color; + } + + set backgroundAlpha(value: number) + { + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.backgroundAlpha has been deprecated, use renderer.background.alpha instead.'); + // #endif + + this.background.alpha = value; + } + + get powerPreference(): WebGLPowerPreference + { + // #if _DEBUG + // eslint-disable-next-line max-len + deprecation('6.4.0', 'renderer.powerPreference has been deprecated, we can only know this if pixi creates the context'); + // #endif + + return this.context.powerPreference; + } + + /** + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. + * @param displayObject - The displayObject the object will be generated from. + * @param {object} options - Generate texture options. + * @param {PIXI.SCALE_MODES} options.scaleMode - The scale mode of the texture. + * @param {number} options.resolution - The resolution / device pixel ratio of the texture being generated. + * @param {PIXI.Rectangle} options.region - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @param {PIXI.MSAA_QUALITY} options.multisample - The number of samples of the frame buffer. + * @returns A texture of the graphics object. + */ + generateTexture(displayObject: IRenderableObject, options?: IGenerateTextureOptions): RenderTexture; + + /** + * Please use the options argument instead. + * @deprecated Since 6.1.0 + * @param displayObject - The displayObject the object will be generated from. + * @param scaleMode - The scale mode of the texture. + * @param resolution - The resolution / device pixel ratio of the texture being generated. + * @param region - The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + * @returns A texture of the graphics object. + */ + generateTexture( + displayObject: IRenderableObject, + scaleMode?: SCALE_MODES, + resolution?: number, + region?: Rectangle): RenderTexture; + + /** + * @ignore + */ + generateTexture(displayObject: IRenderableObject, + options: IGenerateTextureOptions | SCALE_MODES = {}, + resolution?: number, region?: Rectangle): RenderTexture + { + // @deprecated parameters spread, use options instead + if (typeof options === 'number') + { + // #if _DEBUG + deprecation('6.1.0', 'generateTexture options (scaleMode, resolution, region) are now object options.'); + // #endif + + options = { scaleMode: options, resolution, region }; + } + + return this.textureGenerator.generateTexture(displayObject, options); + } + /** * Collection of installed plugins. These are included by default in PIXI, but can be excluded * by creating a custom build. Consult the README for more information about creating custom diff --git a/packages/core/src/autoDetectRenderer.ts b/packages/core/src/autoDetectRenderer.ts index f8e32388e7..12d417cd3c 100644 --- a/packages/core/src/autoDetectRenderer.ts +++ b/packages/core/src/autoDetectRenderer.ts @@ -1,5 +1,5 @@ +import { IRenderer, IRendererOptions } from './IRenderer'; import { Renderer } from './Renderer'; -import type { AbstractRenderer, IRendererOptions } from './AbstractRenderer'; export interface IRendererOptionsAuto extends IRendererOptions { @@ -36,7 +36,7 @@ export interface IRendererOptionsAuto extends IRendererOptions * for devices with dual graphics card **webgl only** * @returns {PIXI.Renderer|PIXI.CanvasRenderer} Returns WebGL renderer if available, otherwise CanvasRenderer */ -export function autoDetectRenderer(options?: IRendererOptionsAuto): AbstractRenderer +export function autoDetectRenderer(options?: IRendererOptionsAuto): IRenderer { return Renderer.create(options); } diff --git a/packages/core/src/background/BackgroundSystem.ts b/packages/core/src/background/BackgroundSystem.ts new file mode 100644 index 0000000000..a2324b9129 --- /dev/null +++ b/packages/core/src/background/BackgroundSystem.ts @@ -0,0 +1,125 @@ +import { deprecation, hex2rgb, hex2string } from '@pixi/utils'; +import { ISystem } from '../system/ISystem'; + +export interface BackgroundOptions +{ + /** the main canvas background alpha. From 0 (fully transparent) to 1 (fully opaque). */ + alpha: number, + /** the main canvas background color. */ + color: number, + /** sets if the renderer will clear the canvas or not before the new render pass. */ + clearBeforeRender: boolean + /** @deprecated The method should not be used */ + transparent?: boolean +} + +/** + * The background system manages the background color and alpha of the main view. + * @memberof PIXI + */ +export class BackgroundSystem implements ISystem +{ + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent PixiJS will use a canvas sized fillRect operation every + * frame to set the canvas background color. If the scene is transparent PixiJS will use clearRect + * to clear the canvas every frame. Disable this by setting this to false. For example, if + * your game has a canvas filling background image you often don't need this set. + * @member {boolean} + * @default + */ + public clearBeforeRender: boolean; + + private _backgroundColorString: string; + private _backgroundColorRgba: number[]; + private _backgroundColor: number; + + constructor() + { + this.clearBeforeRender = true; + + this._backgroundColor = 0x000000; + + this._backgroundColorRgba = [0, 0, 0, 1]; + + this._backgroundColorString = '#000000'; + + this.color = this._backgroundColor; // run bg color setter + this.alpha = 1; + } + + /** + * initiates the background system + * @param {BackgroundOptions} options - the options for the background colors + */ + init(options: BackgroundOptions): void + { + if (options.transparent !== undefined) + { + // #if _DEBUG + deprecation('6.0.0', 'Option transparent is deprecated, please use backgroundAlpha instead.'); + // #endif + + options.alpha = options.transparent ? 0 : 1; + } + + this.clearBeforeRender = options.clearBeforeRender; + this.color = options.color || this._backgroundColor; // run bg color setter + this.alpha = options.alpha; + } + + /** + * The background color to fill if not transparent + * @member {number} + */ + get color(): number + { + return this._backgroundColor; + } + + set color(value: number) + { + this._backgroundColor = value; + this._backgroundColorString = hex2string(value); + hex2rgb(value, this._backgroundColorRgba); + } + + /** + * The background color alpha. Setting this to 0 will make the canvas transparent. + * @member {number} + */ + get alpha(): number + { + return this._backgroundColorRgba[3]; + } + + set alpha(value: number) + { + this._backgroundColorRgba[3] = value; + } + + /** + * The background color as an [R, G, B, A] array. + * @member {number[]} + * @protected + */ + get colorRgba(): number[] + { + return this._backgroundColorRgba; + } + + /** + * The background color as a string. + * @member {string} + * @protected + */ + get colorString(): string + { + return this._backgroundColorString; + } + + destroy(): void + { + // ka boom! + } +} diff --git a/packages/core/src/batch/BatchSystem.ts b/packages/core/src/batch/BatchSystem.ts index cfc89f94c7..0750cc0284 100644 --- a/packages/core/src/batch/BatchSystem.ts +++ b/packages/core/src/batch/BatchSystem.ts @@ -1,6 +1,6 @@ import { ObjectRenderer } from './ObjectRenderer'; -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; import type { Renderer } from '../Renderer'; import type { BaseTexture } from '../textures/BaseTexture'; import type { BatchTextureArray } from './BatchTextureArray'; diff --git a/packages/core/src/batch/ObjectRenderer.ts b/packages/core/src/batch/ObjectRenderer.ts index 7712420eb2..979d770646 100644 --- a/packages/core/src/batch/ObjectRenderer.ts +++ b/packages/core/src/batch/ObjectRenderer.ts @@ -1,4 +1,4 @@ -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; import type { Renderer } from '../Renderer'; /** diff --git a/packages/core/src/context/ContextSystem.ts b/packages/core/src/context/ContextSystem.ts index e157924a62..da12426110 100644 --- a/packages/core/src/context/ContextSystem.ts +++ b/packages/core/src/context/ContextSystem.ts @@ -1,10 +1,10 @@ import { ENV } from '@pixi/constants'; import { settings } from '../settings'; -import type { ISystem } from '../ISystem'; -import type { IRenderingContext } from '../IRenderingContext'; +import type { ISystem } from '../system/ISystem'; import type { Renderer } from '../Renderer'; import type { WebGLExtensions } from './WebGLExtensions'; +import { IRenderingContext } from '../IRenderer'; let CONTEXT_UID_COUNTER = 0; @@ -13,6 +13,20 @@ export interface ISupportDict uint32Indices: boolean; } +export interface ContextOptions +{ + context?: IRenderingContext; + /** + * Use premultipliedAlpha instead + * @deprecated + */ + useContextAlpha?: boolean | 'notMultiplied'; + premultipliedAlpha?: boolean; + powerPreference?: WebGLPowerPreference; + preserveDrawingBuffer?: boolean; + antialias?: boolean; +} + /** * System plugin to the renderer to manage the context. * @memberof PIXI @@ -33,6 +47,17 @@ export class ContextSystem implements ISystem */ readonly supports: ISupportDict; + preserveDrawingBuffer: boolean; + powerPreference: WebGLPowerPreference; + + /** + * Pass-thru setting for the canvas' context `alpha` property. This is typically + * not something you need to fiddle with. If you want transparency, use `backgroundAlpha`. + * @member {boolean} + * @deprecated since 6.4.0 + */ + useContextAlpha: boolean | 'notMultiplied'; + protected CONTEXT_UID: number; protected gl: IRenderingContext; @@ -66,9 +91,6 @@ export class ContextSystem implements ISystem // Bind functions this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); - - (renderer.view as any).addEventListener('webglcontextlost', this.handleContextLost, false); - renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); } /** @@ -97,6 +119,35 @@ export class ContextSystem implements ISystem } } + init(options: ContextOptions): void + { + /* + * The options passed in to create a new WebGL context. + */ + if (options.context) + { + this.initFromContext(options.context); + } + else + { + const alpha = this.renderer.background.alpha < 1; + const premultipliedAlpha = options.premultipliedAlpha ?? true; + + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.useContextAlpha = options.useContextAlpha; + this.powerPreference = options.powerPreference; + + this.initFromOptions({ + alpha, + premultipliedAlpha, + antialias: options.antialias, + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + powerPreference: options.powerPreference, + }); + } + } + /** * Initializes the context. * @protected @@ -109,6 +160,9 @@ export class ContextSystem implements ISystem this.renderer.gl = gl; this.renderer.CONTEXT_UID = CONTEXT_UID_COUNTER++; this.renderer.runners.contextChange.emit(gl); + + (this.renderer.view as any).addEventListener('webglcontextlost', this.handleContextLost, false); + this.renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); } /** @@ -248,7 +302,7 @@ export class ContextSystem implements ISystem /** Handle the post-render runner event. */ protected postrender(): void { - if (this.renderer.renderingToScreen) + if (this.renderer.objectRenderer.renderingToScreen) { this.gl.flush(); } diff --git a/packages/core/src/filters/FilterSystem.ts b/packages/core/src/filters/FilterSystem.ts index 6319486a05..9e60a9892c 100644 --- a/packages/core/src/filters/FilterSystem.ts +++ b/packages/core/src/filters/FilterSystem.ts @@ -6,7 +6,7 @@ import { UniformGroup } from '../shader/UniformGroup'; import { DRAW_MODES, CLEAR_MODES, MSAA_QUALITY } from '@pixi/constants'; import { FilterState } from './FilterState'; -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; import type { Filter } from './Filter'; import type { IFilterTarget } from './IFilterTarget'; import type { ISpriteMaskTarget } from './spriteMask/SpriteMaskFilter'; @@ -103,7 +103,7 @@ export class FilterSystem implements ISystem this.defaultFilterStack = [{}] as any; this.texturePool = new RenderTexturePool(); - this.texturePool.setScreenSize(renderer.view); + this.statePool = []; this.quad = new Quad(); @@ -127,6 +127,11 @@ export class FilterSystem implements ISystem this.useMaxPadding = false; } + init(): void + { + this.texturePool.setScreenSize(this.renderer.view); + } + /** * Pushes a set of filters to be applied later to the system. This will redirect further rendering into an * input render-texture for the rest of the filtering pipeline. diff --git a/packages/core/src/framebuffer/FramebufferSystem.ts b/packages/core/src/framebuffer/FramebufferSystem.ts index 236455ba3b..fdcfcb0036 100644 --- a/packages/core/src/framebuffer/FramebufferSystem.ts +++ b/packages/core/src/framebuffer/FramebufferSystem.ts @@ -4,9 +4,9 @@ import { settings } from '../settings'; import { Framebuffer } from './Framebuffer'; import { GLFramebuffer } from './GLFramebuffer'; -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; import type { Renderer } from '../Renderer'; -import type { IRenderingContext } from '../IRenderingContext'; +import { IRenderingContext } from '../IRenderer'; const tempRectangle = new Rectangle(); diff --git a/packages/core/src/framebuffer/MultisampleSystem.ts b/packages/core/src/framebuffer/MultisampleSystem.ts new file mode 100644 index 0000000000..81442951e0 --- /dev/null +++ b/packages/core/src/framebuffer/MultisampleSystem.ts @@ -0,0 +1,72 @@ +import { MSAA_QUALITY } from '@pixi/constants'; +import { ISystem } from '../system/ISystem'; +import { Renderer } from '../Renderer'; +import { IRenderingContext } from '../IRenderer'; + +/** + * System that manages the multisample property on the WebGL renderer + * @memberof PIXI + */ +export class MultisampleSystem implements ISystem +{ + /** + * The number of msaa samples of the canvas. + * @readonly + */ + public multisample: MSAA_QUALITY; + + private renderer: Renderer; + + constructor(renderer: Renderer) + { + this.renderer = renderer; + } + + protected contextChange(gl: IRenderingContext): void + { + let samples; + + if (this.renderer.context.webGLVersion === 1) + { + const framebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + samples = gl.getParameter(gl.SAMPLES); + + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + } + else + { + const framebuffer = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING); + + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null); + + samples = gl.getParameter(gl.SAMPLES); + + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, framebuffer); + } + + if (samples >= MSAA_QUALITY.HIGH) + { + this.multisample = MSAA_QUALITY.HIGH; + } + else if (samples >= MSAA_QUALITY.MEDIUM) + { + this.multisample = MSAA_QUALITY.MEDIUM; + } + else if (samples >= MSAA_QUALITY.LOW) + { + this.multisample = MSAA_QUALITY.LOW; + } + else + { + this.multisample = MSAA_QUALITY.NONE; + } + } + + destroy(): void + { + // ka boom! + } +} diff --git a/packages/core/src/geometry/BufferSystem.ts b/packages/core/src/geometry/BufferSystem.ts index 50feea8400..19d9a07b13 100644 --- a/packages/core/src/geometry/BufferSystem.ts +++ b/packages/core/src/geometry/BufferSystem.ts @@ -1,9 +1,9 @@ import { GLBuffer } from './GLBuffer'; import type { Renderer } from '../Renderer'; -import type { IRenderingContext } from '../IRenderingContext'; import type { Buffer } from './Buffer'; -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; +import { IRenderingContext } from '../IRenderer'; /** * System plugin to the renderer to manage buffers. diff --git a/packages/core/src/geometry/GeometrySystem.ts b/packages/core/src/geometry/GeometrySystem.ts index 5409e8d633..cbe2e1b054 100644 --- a/packages/core/src/geometry/GeometrySystem.ts +++ b/packages/core/src/geometry/GeometrySystem.ts @@ -2,14 +2,14 @@ import { GLBuffer } from './GLBuffer'; import { ENV } from '@pixi/constants'; import { settings } from '../settings'; -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; import type { DRAW_MODES } from '@pixi/constants'; import type { Renderer } from '../Renderer'; -import type { IRenderingContext } from '../IRenderingContext'; import type { Geometry } from './Geometry'; import type { Shader } from '../shader/Shader'; import type { Program } from '../shader/Program'; import type { Dict } from '@pixi/utils'; +import { IRenderingContext } from '../IRenderer'; const byteSizeMap: {[key: number]: number} = { 5126: 4, 5123: 2, 5121: 1 }; diff --git a/packages/core/src/geometry/utils/setVertexAttribArrays.ts b/packages/core/src/geometry/utils/setVertexAttribArrays.ts index ddc09562a9..ca4bfb33bd 100644 --- a/packages/core/src/geometry/utils/setVertexAttribArrays.ts +++ b/packages/core/src/geometry/utils/setVertexAttribArrays.ts @@ -1,5 +1,5 @@ -import type { IRenderingContext } from '../../IRenderingContext'; import type { Dict } from '@pixi/utils'; +import { IRenderingContext } from '../../IRenderer'; // var GL_MAP = {}; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d7cac71bf8..3183318fa7 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,13 +2,14 @@ import './settings'; export * from './textures/resources'; export * from './systems'; -export * from './IRenderingContext'; -export * from './IRenderableObject'; +export * from './IRenderer'; + export * from './autoDetectRenderer'; export * from './fragments'; -export * from './ISystem'; +export * from './system/ISystem'; +export * from './IRenderer'; +export * from './plugin/PluginSystem'; export * from './Renderer'; -export * from './AbstractRenderer'; export * from './framebuffer/Framebuffer'; export * from './framebuffer/GLFramebuffer'; export * from './textures/Texture'; diff --git a/packages/core/src/mask/AbstractMaskSystem.ts b/packages/core/src/mask/AbstractMaskSystem.ts index e729507ea8..d97c4d4aad 100644 --- a/packages/core/src/mask/AbstractMaskSystem.ts +++ b/packages/core/src/mask/AbstractMaskSystem.ts @@ -1,4 +1,4 @@ -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; import type { MaskData } from './MaskData'; import type { Renderer } from '../Renderer'; diff --git a/packages/core/src/mask/MaskSystem.ts b/packages/core/src/mask/MaskSystem.ts index 7eb262c68b..bc6bddafe1 100644 --- a/packages/core/src/mask/MaskSystem.ts +++ b/packages/core/src/mask/MaskSystem.ts @@ -2,7 +2,7 @@ import { MaskData } from './MaskData'; import { SpriteMaskFilter } from '../filters/spriteMask/SpriteMaskFilter'; import { MASK_TYPES } from '@pixi/constants'; -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; import type { IMaskTarget } from './MaskData'; import type { Renderer } from '../Renderer'; diff --git a/packages/core/src/plugin/PluginSystem.ts b/packages/core/src/plugin/PluginSystem.ts new file mode 100644 index 0000000000..a6cb8135a4 --- /dev/null +++ b/packages/core/src/plugin/PluginSystem.ts @@ -0,0 +1,67 @@ +import { IRenderer } from '../IRenderer'; +import { Renderer } from '../Renderer'; +import { ISystem } from '../system/ISystem'; + +export interface IRendererPlugin +{ + destroy(): void; +} + +export interface IRendererPlugins +{ + [key: string]: any; +} + +export interface IRendererPluginConstructor +{ + new (renderer: R, options?: any): IRendererPlugin; +} + +/** + * Manages the functionality that allows users to extend pixi functionality via additional plugins. + * @memberof PIXI + */ +export class PluginSystem implements ISystem +{ + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + public readonly plugins: IRendererPlugins; + private renderer: IRenderer; + + constructor(renderer: IRenderer) + { + this.renderer = renderer; + + /** + * Collection of plugins. + * @readonly + * @member {object} + */ + this.plugins = {}; + } + + /** + * Initialize the plugins. + * @protected + * @param {object} staticMap - The dictionary of statically saved plugins. + */ + init(staticMap: IRendererPlugins): void + { + for (const o in staticMap) + { + this.plugins[o] = new (staticMap[o])(this.renderer); + } + } + + destroy(): void + { + for (const o in this.plugins) + { + this.plugins[o].destroy(); + this.plugins[o] = null; + } + } +} diff --git a/packages/core/src/projection/ProjectionSystem.ts b/packages/core/src/projection/ProjectionSystem.ts index fa7ead0c80..8c1390e66c 100644 --- a/packages/core/src/projection/ProjectionSystem.ts +++ b/packages/core/src/projection/ProjectionSystem.ts @@ -1,6 +1,6 @@ import { Matrix } from '@pixi/math'; -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; import type { Rectangle } from '@pixi/math'; import type { Renderer } from '../Renderer'; diff --git a/packages/core/src/render/ObjectRendererSystem.ts b/packages/core/src/render/ObjectRendererSystem.ts new file mode 100644 index 0000000000..a8d3e725b5 --- /dev/null +++ b/packages/core/src/render/ObjectRendererSystem.ts @@ -0,0 +1,124 @@ +import { Matrix } from '@pixi/math'; +import { IRenderableObject, IRendererRenderOptions } from '../IRenderer'; +import { ISystem } from '../system/ISystem'; +import { Renderer } from '../Renderer'; +import { RenderTexture } from '../renderTexture/RenderTexture'; + +/** + * system that provides a render function that focussing on rendering Pixi Scene Graph objects + * to either the main view or to a renderTexture. Used for Canvas `WebGL` contexts + * @memberof PIXI + */ +export class ObjectRendererSystem implements ISystem +{ + renderer: Renderer; + + /** + * Flag if we are rendering to the screen vs renderTexture + * @readonly + * @default true + */ + renderingToScreen: boolean; + + /** + * the last object rendered by the renderer. Useful for other plugins like interaction managers + * @readonly + */ + lastObjectRendered: IRenderableObject; + + // renderers scene graph! + constructor(renderer: Renderer) + { + this.renderer = renderer; + } + + /** + * Renders the object to its WebGL view. + * @param displayObject - The object to be rendered. + * @param options - the options to be passed to the renderer + */ + render(displayObject: IRenderableObject, options?: IRendererRenderOptions): void + { + const renderer = this.renderer; + + let renderTexture: RenderTexture; + let clear: boolean; + let transform: Matrix; + let skipUpdateTransform: boolean; + + if (options) + { + renderTexture = options.renderTexture; + clear = options.clear; + transform = options.transform; + skipUpdateTransform = options.skipUpdateTransform; + } + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + renderer.runners.prerender.emit(); + renderer.emit('prerender'); + + // apply a transform at a GPU level + renderer.projection.transform = transform; + + // no point rendering if our context has been blown up! + if (renderer.context.isLost) + { + return; + } + + if (!renderTexture) + { + this.lastObjectRendered = displayObject; + } + + if (!skipUpdateTransform) + { + // update the scene graph + const cacheParent = displayObject.enableTempParent(); + + displayObject.updateTransform(); + displayObject.disableTempParent(cacheParent); + // displayObject.hitArea = //TODO add a temp hit area + } + + renderer.renderTexture.bind(renderTexture); + renderer.batch.currentRenderer.start(); + + if (clear !== undefined ? clear : renderer.background.clearBeforeRender) + { + renderer.renderTexture.clear(); + } + + displayObject.render(renderer); + + // apply transform.. + renderer.batch.currentRenderer.flush(); + + if (renderTexture) + { + if (options.blit) + { + renderer.framebuffer.blit(); + } + + renderTexture.baseTexture.update(); + } + + renderer.runners.postrender.emit(); + + // reset transform after render + renderer.projection.transform = null; + + renderer.emit('postrender'); + } + + destroy(): void + { + // ka pow! + this.renderer = null; + this.lastObjectRendered = null; + } +} diff --git a/packages/core/src/renderTexture/GenerateTextureSystem.ts b/packages/core/src/renderTexture/GenerateTextureSystem.ts new file mode 100644 index 0000000000..440619c08c --- /dev/null +++ b/packages/core/src/renderTexture/GenerateTextureSystem.ts @@ -0,0 +1,89 @@ +import { MSAA_QUALITY, SCALE_MODES } from '@pixi/constants'; +import { Matrix, Rectangle, Transform } from '@pixi/math'; +import { IRenderer, IRenderableContainer, IRenderableObject } from '../IRenderer'; +import { ISystem } from '../system/ISystem'; +import { RenderTexture } from './RenderTexture'; + +const tempTransform = new Transform(); + +// TODO could this just be part of extract? +export interface IGenerateTextureOptions +{ + /** The scale mode of the texture. Optional, defaults to `PIXI.settings.SCALE_MODE`. */ + scaleMode?: SCALE_MODES; + /** The resolution / device pixel ratio of the texture being generated. Optional defaults to Renderer resolution. */ + resolution?: number; + /** + * The region of the displayObject, that shall be rendered, + * if no region is specified, defaults to the local bounds of the displayObject. + */ + region?: Rectangle; + /** The number of samples of the frame buffer. */ + multisample?: MSAA_QUALITY; +} + +/** + * System that manages the generation of textures from the renderer. + * @memberof PIXI + */ +export class GenerateTextureSystem implements ISystem +{ + renderer: IRenderer; + + private readonly _tempMatrix: Matrix; + + constructor(renderer: IRenderer) + { + this.renderer = renderer; + + this._tempMatrix = new Matrix(); + } + + /** + * A Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. + * @param displayObject - The displayObject the object will be generated from. + * @param {IGenerateTextureOptions} options - Generate texture options. + * @returns a shiny new texture of the display object passed in + */ + generateTexture(displayObject: IRenderableObject, options: IGenerateTextureOptions): RenderTexture + { + const { region: manualRegion, ...textureOptions } = options; + + const region = manualRegion || (displayObject as IRenderableContainer).getLocalBounds(null, true); + + // minimum texture size is 1x1, 0x0 will throw an error + if (region.width === 0) region.width = 1; + if (region.height === 0) region.height = 1; + + const renderTexture = RenderTexture.create( + { + width: region.width, + height: region.height, + ...textureOptions, + }); + + this._tempMatrix.tx = -region.x; + this._tempMatrix.ty = -region.y; + + const transform = displayObject.transform; + + displayObject.transform = tempTransform; + + this.renderer.render(displayObject, { + renderTexture, + transform: this._tempMatrix, + skipUpdateTransform: !!displayObject.parent, + blit: true, + }); + + displayObject.transform = transform; + + return renderTexture; + } + + destroy(): void + { + // ka boom! + } +} diff --git a/packages/core/src/renderTexture/RenderTextureSystem.ts b/packages/core/src/renderTexture/RenderTextureSystem.ts index cc04f2387f..f386afae0c 100644 --- a/packages/core/src/renderTexture/RenderTextureSystem.ts +++ b/packages/core/src/renderTexture/RenderTextureSystem.ts @@ -1,7 +1,7 @@ import { Rectangle } from '@pixi/math'; import { BUFFER_BITS } from '@pixi/constants'; -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; import type { Renderer } from '../Renderer'; import type { RenderTexture } from './RenderTexture'; import type { BaseRenderTexture } from './BaseRenderTexture'; @@ -36,9 +36,6 @@ export class RenderTextureSystem implements ISystem { /* eslint-enable max-len */ - /** The clear background color as RGBA. */ - public clearColor: number[]; - /** * List of masks for the {@link PIXI.StencilSystem}. * @readonly @@ -81,7 +78,6 @@ export class RenderTextureSystem implements ISystem { this.renderer = renderer; - this.clearColor = renderer._backgroundColorRgba; this.defaultMaskStack = []; this.current = null; this.sourceFrame = new Rectangle(); @@ -137,8 +133,8 @@ export class RenderTextureSystem implements ISystem if (!sourceFrame) { - tempRect.width = renderer.screen.width; - tempRect.height = renderer.screen.height; + tempRect.width = renderer._view.screen.width; + tempRect.height = renderer._view.screen.height; sourceFrame = tempRect; } @@ -196,11 +192,11 @@ export class RenderTextureSystem implements ISystem } else { - clearColor = clearColor || this.clearColor; + clearColor = clearColor || this.renderer.background.colorRgba; } const destinationFrame = this.destinationFrame; - const baseFrame: ISize = this.current ? this.current.baseTexture : this.renderer.screen; + const baseFrame: ISize = this.current ? this.current.baseTexture : this.renderer._view.screen; const clearMask = destinationFrame.width !== baseFrame.width || destinationFrame.height !== baseFrame.height; if (clearMask) diff --git a/packages/core/src/shader/ShaderSystem.ts b/packages/core/src/shader/ShaderSystem.ts index 94da759978..f3fda3d3b2 100644 --- a/packages/core/src/shader/ShaderSystem.ts +++ b/packages/core/src/shader/ShaderSystem.ts @@ -1,9 +1,8 @@ import { GLProgram } from './GLProgram'; import { generateUniformsSync, unsafeEvalSupported } from './utils'; -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; import type { Renderer } from '../Renderer'; -import type { IRenderingContext } from '../IRenderingContext'; import type { Shader } from './Shader'; import type { Program } from './Program'; import type { UniformGroup } from './UniformGroup'; @@ -12,6 +11,7 @@ import type { UniformsSyncCallback } from './utils'; import { generateUniformBufferSync } from './utils/generateUniformBufferSync'; import { generateProgram } from './utils/generateProgram'; +import { IRenderingContext } from '../IRenderer'; let UID = 0; // default sync data so we don't create a new one each time! @@ -63,7 +63,7 @@ export class ShaderSystem implements ISystem * throwing an error if platform doesn't support unsafe-evals. * @private */ - systemCheck(): void + private systemCheck(): void { if (!unsafeEvalSupported()) { diff --git a/packages/core/src/shader/utils/checkMaxIfStatementsInShader.ts b/packages/core/src/shader/utils/checkMaxIfStatementsInShader.ts index 208cf90adb..95d742e5e2 100644 --- a/packages/core/src/shader/utils/checkMaxIfStatementsInShader.ts +++ b/packages/core/src/shader/utils/checkMaxIfStatementsInShader.ts @@ -1,4 +1,4 @@ -import type { IRenderingContext } from '../../IRenderingContext'; +import { IRenderingContext } from '../../IRenderer'; const fragTemplate = [ 'precision mediump float;', diff --git a/packages/core/src/shader/utils/generateProgram.ts b/packages/core/src/shader/utils/generateProgram.ts index 4d85b64ebf..76fb14e527 100644 --- a/packages/core/src/shader/utils/generateProgram.ts +++ b/packages/core/src/shader/utils/generateProgram.ts @@ -1,5 +1,4 @@ import { Program } from '../Program'; -import type { IRenderingContext } from '../../IRenderingContext'; import type { IGLUniformData } from '../GLProgram'; import { GLProgram } from '../GLProgram'; import { compileShader } from './compileShader'; @@ -7,6 +6,7 @@ import { defaultValue } from './defaultValue'; import { getAttributeData } from './getAttributeData'; import { getUniformData } from './getUniformData'; import { logProgramError } from './logProgramError'; +import { IRenderingContext } from '../../IRenderer'; /** * generates a WebGL Program object from a high level Pixi Program. diff --git a/packages/core/src/startup/StartupSystem.ts b/packages/core/src/startup/StartupSystem.ts new file mode 100644 index 0000000000..0a8e38f5e9 --- /dev/null +++ b/packages/core/src/startup/StartupSystem.ts @@ -0,0 +1,49 @@ +import { sayHello } from '@pixi/utils'; +import type { BackgroundOptions } from '../background/BackgroundSystem'; +import type { ViewOptions } from '../view/ViewSystem'; +import type { IRendererPlugins } from '../plugin/PluginSystem'; +import { IRenderer } from '../IRenderer'; +import { ISystem } from '../system/ISystem'; +import { ContextOptions } from '../systems'; + +// TODO this can be infered by good use of generics in the future.. +export interface StartupOptions extends Record +{ + _plugin: IRendererPlugins, + background: BackgroundOptions, + _view: ViewOptions, + context?: ContextOptions +} + +/** + * A simple system responsible for initiating the renderer. + * @memberof PIXI + */export class StartupSystem implements ISystem +{ + readonly renderer: IRenderer; + + constructor(renderer: IRenderer) + { + this.renderer = renderer; + } + + /** + * It all starts here! This initiates every system, passing in the options for any system by name. + * @param options - the config for the renderer and all its systems + */ + run(options: StartupOptions): void + { + const renderer = this.renderer; + + renderer.emitWithCustomOptions(renderer.runners.init, options); + + sayHello(renderer.rendererLogId); + + renderer.resize(this.renderer.screen.width, this.renderer.screen.height); + } + + destroy(): void + { + // ka pow! + } +} diff --git a/packages/core/src/state/StateSystem.ts b/packages/core/src/state/StateSystem.ts index f7cd3654eb..91aa6deba5 100644 --- a/packages/core/src/state/StateSystem.ts +++ b/packages/core/src/state/StateSystem.ts @@ -2,8 +2,8 @@ import { mapWebGLBlendModesToPixi } from './utils/mapWebGLBlendModesToPixi'; import { State } from './State'; import { BLEND_MODES } from '@pixi/constants'; -import type { ISystem } from '../ISystem'; -import type { IRenderingContext } from '../IRenderingContext'; +import type { ISystem } from '../system/ISystem'; +import { IRenderingContext } from '../IRenderer'; const BLEND = 0; const OFFSET = 1; diff --git a/packages/core/src/ISystem.ts b/packages/core/src/system/ISystem.ts similarity index 82% rename from packages/core/src/ISystem.ts rename to packages/core/src/system/ISystem.ts index 2fafe5dcc1..c1b13e2ecf 100644 --- a/packages/core/src/ISystem.ts +++ b/packages/core/src/system/ISystem.ts @@ -1,14 +1,15 @@ -import type { Renderer } from './Renderer'; +import type { Renderer } from '../Renderer'; import { deprecation } from '@pixi/utils'; /** * Interface for systems used by the {@link PIXI.Renderer}. * @memberof PIXI */ -export interface ISystem +export interface ISystem { + init?(options?: INIT_OPTIONS): void; /** Generic destroy methods to be overridden by the subclass */ - destroy(): void; + destroy?(options?: DESTROY_OPTIONS): void; } /** diff --git a/packages/core/src/system/SystemManager.ts b/packages/core/src/system/SystemManager.ts new file mode 100644 index 0000000000..dcfdf64d0d --- /dev/null +++ b/packages/core/src/system/SystemManager.ts @@ -0,0 +1,146 @@ +import { Runner } from '@pixi/runner'; +import { EventEmitter } from '@pixi/utils'; +import { IRenderer } from '../IRenderer'; +import { ISystem, ISystemConstructor } from './ISystem'; +interface ISystemConfig +{ + runners: string[], + systems: Record> +} + +/** + * The SystemManager is a class that provides functions for managing a set of systems + * This is a base class, that is generic (no render code or knowledge at all) + * @memberof PIXI + */ +export class SystemManager extends EventEmitter +{ + /** a collection of runners defined by the user */ + readonly runners: {[key: string]: Runner} = {}; + + private _systemsHash: Record = {}; + + /** + * Set up a system with a collection of SystemClasses and runners. + * Systems are attached dynamically to this class when added. + * @param config - the config for the system manager + */ + setup(config: ISystemConfig): void + { + this.addRunners(...config.runners); + + let i: keyof typeof config.systems; + + for (i in config.systems) + { + // @zyie not sure about the TS here.. + this.addSystem(config.systems[i], i); + } + } + + /** + * Create a bunch of runners based of a collection of ids + * @param runnerIds - the runner ids to add + */ + addRunners(...runnerIds: string[]): void + { + runnerIds.forEach((runnerId) => + { + this.runners[runnerId] = new Runner(runnerId); + }); + } + + /** + * Add a new system to the renderer. + * @param ClassRef - Class reference + * @param name - Property name for system, if not specified + * will use a static `name` property on the class itself. This + * name will be assigned as s property on the Renderer so make + * sure it doesn't collide with properties on Renderer. + * @returns Return instance of renderer + */ + addSystem(ClassRef: ISystemConstructor, name: string): this + { + const system = new ClassRef(this as any as R); + + if ((this as any)[name]) + { + throw new Error(`Whoops! The name "${name}" is already in use`); + } + + (this as any)[name] = system; + + this._systemsHash[name] = system; + + for (const i in this.runners) + { + this.runners[i].add(system); + } + + /** + * Fired after rendering finishes. + * @event PIXI.Renderer#postrender + */ + + /** + * Fired before rendering starts. + * @event PIXI.Renderer#prerender + */ + + /** + * Fired when the WebGL context is set. + * @event PIXI.Renderer#context + * @param {WebGLRenderingContext} gl - WebGL context. + */ + + return this; + } + + /** + * A function that will run a runner and call the runners function but pass in different options + * to each system based on there name. + * + * eg if you have two systems added called `systemA` and `systemB` you could call do the following: + * + * ``` + * system.emitWithCustomOptions(init, { + * systemA: {...optionsForA}, + * systemB: {...optionsForB} + * }) + * + * init would be called on system A passing options.A and init would be called on system B passing options.B + * ``` + * @param runner - the runner to target + * @param options - key value options for each system + */ + emitWithCustomOptions(runner: Runner, options: Record): void + { + const systemHashKeys = Object.keys(this._systemsHash); + + runner.items.forEach((system) => + { + // I know this does not need to be a performant function so it.. isn't! + // its only used for init and destroy.. we can refactor if required.. + const systemName = systemHashKeys.find((systemId) => this._systemsHash[systemId] === system); + + system[runner.name](options[systemName]); + }); + } + + /** destroy the all runners and systems. Its apps job to */ + destroy(): void + { + Object.values(this.runners).forEach((runner) => + { + runner.destroy(); + }); + + this._systemsHash = {}; + } + + // TODO implement! + // removeSystem(ClassRef: ISystemConstructor, name: string): void + // { + + // } +} diff --git a/packages/core/src/systems.ts b/packages/core/src/systems.ts index d0c573b601..e97b7ed096 100644 --- a/packages/core/src/systems.ts +++ b/packages/core/src/systems.ts @@ -12,3 +12,9 @@ export * from './shader/ShaderSystem'; export * from './state/StateSystem'; export * from './textures/TextureGCSystem'; export * from './textures/TextureSystem'; +export * from './renderTexture/GenerateTextureSystem'; +export * from './background/BackgroundSystem'; +export * from './view/ViewSystem'; +export * from './plugin/PluginSystem'; +export * from './system/SystemManager'; +export * from './startup/StartupSystem'; diff --git a/packages/core/src/textures/TextureGCSystem.ts b/packages/core/src/textures/TextureGCSystem.ts index 26a5880de9..4f2bc76268 100644 --- a/packages/core/src/textures/TextureGCSystem.ts +++ b/packages/core/src/textures/TextureGCSystem.ts @@ -1,7 +1,7 @@ import { GC_MODES } from '@pixi/constants'; import { settings } from '@pixi/settings'; -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; import type { Renderer } from '../Renderer'; import type { Texture } from './Texture'; import type { RenderTexture } from '../renderTexture/RenderTexture'; @@ -68,7 +68,7 @@ export class TextureGCSystem implements ISystem */ protected postrender(): void { - if (!this.renderer.renderingToScreen) + if (!this.renderer.objectRenderer.renderingToScreen) { return; } diff --git a/packages/core/src/textures/TextureSystem.ts b/packages/core/src/textures/TextureSystem.ts index d23e6a1f0a..0a9f8e4d37 100644 --- a/packages/core/src/textures/TextureSystem.ts +++ b/packages/core/src/textures/TextureSystem.ts @@ -4,10 +4,11 @@ import { GLTexture } from './GLTexture'; import { removeItems } from '@pixi/utils'; import { MIPMAP_MODES, WRAP_MODES, SCALE_MODES, TYPES, SAMPLER_TYPES } from '@pixi/constants'; -import type { ISystem } from '../ISystem'; +import type { ISystem } from '../system/ISystem'; import type { Texture } from './Texture'; -import type { IRenderingContext } from '../IRenderingContext'; + import type { Renderer } from '../Renderer'; +import { IRenderingContext } from '../IRenderer'; /** * System plugin to the renderer to manage textures. diff --git a/packages/core/src/view/ViewSystem.ts b/packages/core/src/view/ViewSystem.ts new file mode 100644 index 0000000000..9c1dcc4eab --- /dev/null +++ b/packages/core/src/view/ViewSystem.ts @@ -0,0 +1,128 @@ +import { Rectangle } from '@pixi/math'; +import { settings } from '@pixi/settings'; +import { IRenderer } from '../IRenderer'; +import { ISystem } from '../system/ISystem'; + +/** + * Options passed to the ViewSystem + * @memberof PIXI + */ +export interface ViewOptions +{ + /** The width of the screen. */ + width: number + /** The height of the screen. */ + height: number + /** The canvas to use as a view, optional. */ + view?: HTMLCanvasElement; + /** Resizes renderer view in CSS pixels to allow for resolutions other than 1. */ + autoDensity?: boolean + /** The resolution / device pixel ratio of the renderer. */ + resolution?: number +} + +/** + * The view system manages the main canvas that is attached to the DOM. + * This main role is to deal with how the holding the view reference and dealing with how it is resized. + * @memberof PIXI + */ +export class ViewSystem implements ISystem +{ + private renderer: IRenderer; + + /** + * The resolution / device pixel ratio of the renderer. + * @member {number} + * @default PIXI.settings.RESOLUTION + */ + public resolution: number; + + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight). + * + * Its safe to use as filterArea or hitArea for the whole stage. + * @member {PIXI.Rectangle} + */ + public screen: Rectangle; + + /** + * The canvas element that everything is drawn to. + * @member {HTMLCanvasElement} + */ + public element: HTMLCanvasElement; + + /** + * Whether CSS dimensions of canvas view should be resized to screen dimensions automatically. + * @member {boolean} + */ + public autoDensity: boolean; + + constructor(renderer: IRenderer) + { + this.renderer = renderer; + } + + /** + * initiates the view system + * @param {PIXI.ViewOptions} options - the options for the view + */ + init(options: ViewOptions): void + { + this.screen = new Rectangle(0, 0, options.width, options.height); + + this.element = options.view || document.createElement('canvas'); + + this.resolution = options.resolution || settings.RESOLUTION; + + this.autoDensity = !!options.autoDensity; + } + + /** + * Resizes the screen and canvas to the specified dimensions. + * @param desiredScreenWidth - The new width of the screen. + * @param desiredScreenHeight - The new height of the screen. + */ + resizeView(desiredScreenWidth: number, desiredScreenHeight: number): void + { + this.element.width = Math.round(desiredScreenWidth * this.resolution); + this.element.height = Math.round(desiredScreenHeight * this.resolution); + + const screenWidth = this.element.width / this.resolution; + const screenHeight = this.element.height / this.resolution; + + this.screen.width = screenWidth; + this.screen.height = screenHeight; + + if (this.autoDensity) + { + this.element.style.width = `${screenWidth}px`; + this.element.style.height = `${screenHeight}px`; + } + + /** + * Fired after view has been resized. + * @event PIXI.Renderer#resize + * @param {number} screenWidth - The new width of the screen. + * @param {number} screenHeight - The new height of the screen. + */ + this.renderer.emit('resize', screenWidth, screenHeight); + this.renderer.runners.resize.emit(this.screen.height, this.screen.width); + } + + /** + * Destroys this System and optionally removes the canvas from the dom. + * @param {boolean} [removeView=false] - Whether to remove the canvas from the DOM. + */ + destroy(removeView: boolean): void + { + // ka boom! + if (removeView && this.element.parentNode) + { + this.element.parentNode.removeChild(this.element); + } + + this.renderer = null; + this.element = null; + this.screen = null; + } +} diff --git a/packages/core/test/MaskSystem.tests.ts b/packages/core/test/MaskSystem.tests.ts index cc3f109bd9..00148ee6a9 100644 --- a/packages/core/test/MaskSystem.tests.ts +++ b/packages/core/test/MaskSystem.tests.ts @@ -179,7 +179,7 @@ describe('MaskSystem', () => render() { /* nothing*/ }, } as unknown as IMaskTarget; - renderer.resolution = 2; + renderer._view.resolution = 2; renderer.resize(30, 30); const rt = RenderTexture.create({ width: 20, height: 20, resolution: 3 }); @@ -202,8 +202,9 @@ describe('MaskSystem', () => expect(scissor.args[1]).to.eql([Math.round(7.5), Math.round(12), Math.round(18), Math.round(15)]); rt.destroy(true); + renderer.projection.transform = null; - renderer.resolution = 1; + renderer._view.resolution = 1; }); // eslint-disable-next-line func-names diff --git a/packages/events/src/EventSystem.ts b/packages/events/src/EventSystem.ts index a553bbc187..f242b7650e 100644 --- a/packages/events/src/EventSystem.ts +++ b/packages/events/src/EventSystem.ts @@ -18,7 +18,7 @@ const TOUCH_TO_POINTER: Record = { interface Renderer { - _lastObjectRendered: IRenderableObject; + lastObjectRendered: IRenderableObject; view: HTMLCanvasElement; resolution: number; plugins: Record; @@ -188,7 +188,7 @@ export class EventSystem */ private onPointerDown(nativeEvent: MouseEvent | PointerEvent | TouchEvent): void { - this.rootBoundary.rootTarget = this.renderer._lastObjectRendered as DisplayObject; + this.rootBoundary.rootTarget = this.renderer.lastObjectRendered as DisplayObject; // if we support touch events, then only use those for touch events, not pointer events if (this.supportsTouchEvents && (nativeEvent as PointerEvent).pointerType === 'touch') return; @@ -230,7 +230,7 @@ export class EventSystem */ private onPointerMove(nativeEvent: MouseEvent | PointerEvent | TouchEvent): void { - this.rootBoundary.rootTarget = this.renderer._lastObjectRendered as DisplayObject; + this.rootBoundary.rootTarget = this.renderer.lastObjectRendered as DisplayObject; // if we support touch events, then only use those for touch events, not pointer events if (this.supportsTouchEvents && (nativeEvent as PointerEvent).pointerType === 'touch') return; @@ -253,7 +253,7 @@ export class EventSystem */ private onPointerUp(nativeEvent: MouseEvent | PointerEvent | TouchEvent): void { - this.rootBoundary.rootTarget = this.renderer._lastObjectRendered as DisplayObject; + this.rootBoundary.rootTarget = this.renderer.lastObjectRendered as DisplayObject; // if we support touch events, then only use those for touch events, not pointer events if (this.supportsTouchEvents && (nativeEvent as PointerEvent).pointerType === 'touch') return; @@ -279,7 +279,7 @@ export class EventSystem */ private onPointerOverOut(nativeEvent: MouseEvent | PointerEvent | TouchEvent): void { - this.rootBoundary.rootTarget = this.renderer._lastObjectRendered as DisplayObject; + this.rootBoundary.rootTarget = this.renderer.lastObjectRendered as DisplayObject; // if we support touch events, then only use those for touch events, not pointer events if (this.supportsTouchEvents && (nativeEvent as PointerEvent).pointerType === 'touch') return; @@ -304,7 +304,7 @@ export class EventSystem { const wheelEvent = this.normalizeWheelEvent(nativeEvent); - this.rootBoundary.rootTarget = this.renderer._lastObjectRendered as DisplayObject; + this.rootBoundary.rootTarget = this.renderer.lastObjectRendered as DisplayObject; this.rootBoundary.mapEvent(wheelEvent); } diff --git a/packages/interaction/src/InteractionManager.ts b/packages/interaction/src/InteractionManager.ts index 11cba6d0e7..91ac0c728e 100644 --- a/packages/interaction/src/InteractionManager.ts +++ b/packages/interaction/src/InteractionManager.ts @@ -7,9 +7,9 @@ import { TreeSearch } from './TreeSearch'; import { EventEmitter } from '@pixi/utils'; import { interactiveTarget } from './interactiveTarget'; -import type { AbstractRenderer } from '@pixi/core'; import type { Point, IPointData } from '@pixi/math'; import type { Dict } from '@pixi/utils'; +import { IRenderer } from '@pixi/core'; // Mix interactiveTarget into DisplayObject.prototype DisplayObject.mixin(interactiveTarget); @@ -101,7 +101,7 @@ export class InteractionManager extends EventEmitter public search: TreeSearch; /** The renderer this interaction manager works for. */ - public renderer: AbstractRenderer; + public renderer: IRenderer; /** * Should default browser actions automatically be prevented. @@ -185,7 +185,7 @@ export class InteractionManager extends EventEmitter * @param {number} [options.interactionFrequency=10] - Maximum frequency (ms) at pointer over/out states will be checked. * @param {number} [options.useSystemTicker=true] - Whether to add {@link tickerUpdate} to {@link PIXI.Ticker.system}. */ - constructor(renderer: AbstractRenderer, options?: InteractionManagerOptions) + constructor(renderer: IRenderer, options?: InteractionManagerOptions) { super(); @@ -677,7 +677,7 @@ export class InteractionManager extends EventEmitter */ get lastObjectRendered(): DisplayObject { - return (this.renderer._lastObjectRendered as DisplayObject) || this._tempDisplayObject; + return (this.renderer.lastObjectRendered as DisplayObject) || this._tempDisplayObject; } /** diff --git a/packages/mixin-cache-as-bitmap/global.d.ts b/packages/mixin-cache-as-bitmap/global.d.ts index 83f5cd3024..cd83cbab65 100644 --- a/packages/mixin-cache-as-bitmap/global.d.ts +++ b/packages/mixin-cache-as-bitmap/global.d.ts @@ -13,8 +13,8 @@ declare namespace GlobalMixins _initCachedDisplayObject(renderer: import('@pixi/core').Renderer): void; _calculateCachedBounds(): void; _getCachedLocalBounds(): import('@pixi/math').Rectangle; - _renderCachedCanvas(renderer: import('@pixi/core').AbstractRenderer): void; - _initCachedDisplayObjectCanvas(renderer: import('@pixi/core').AbstractRenderer): void; + _renderCachedCanvas(renderer: import('@pixi/core').IRenderer): void; + _initCachedDisplayObjectCanvas(renderer: import('@pixi/core').IRenderer): void; _destroyCachedDisplayObject(): void; _cacheAsBitmapDestroy(options?: import('@pixi/display').IDestroyOptions | boolean): void; } diff --git a/packages/mixin-cache-as-bitmap/src/index.ts b/packages/mixin-cache-as-bitmap/src/index.ts index 6d0df080e2..f5f0b95330 100644 --- a/packages/mixin-cache-as-bitmap/src/index.ts +++ b/packages/mixin-cache-as-bitmap/src/index.ts @@ -1,4 +1,4 @@ -import { Texture, BaseTexture, RenderTexture, Renderer, MaskData, AbstractRenderer } from '@pixi/core'; +import { Texture, BaseTexture, RenderTexture, Renderer, MaskData, IRenderer } from '@pixi/core'; import { Sprite } from '@pixi/sprite'; import { Container, DisplayObject, IDestroyOptions } from '@pixi/display'; import { IPointData, Matrix, Rectangle } from '@pixi/math'; @@ -8,9 +8,11 @@ import { MSAA_QUALITY } from '@pixi/constants'; // Don't import CanvasRender to remove dependency on this optional package // this type should satisify these requirements for cacheAsBitmap types -interface CanvasRenderer extends AbstractRenderer +interface CanvasRenderer extends IRenderer { - context: CanvasRenderingContext2D; + canvasContext: { + activeContext: CanvasRenderingContext2D; + } } const _tempMatrix = new Matrix(); @@ -32,7 +34,7 @@ export class CacheData { public textureCacheId: string; public originalRender: (renderer: Renderer) => void; - public originalRenderCanvas: (renderer: AbstractRenderer) => void; + public originalRenderCanvas: (renderer: IRenderer) => void; public originalCalculateBounds: () => void; public originalGetLocalBounds: (rect?: Rectangle) => Rectangle; public originalUpdateTransform: () => void; @@ -350,7 +352,7 @@ DisplayObject.prototype._initCachedDisplayObject = function _initCachedDisplayOb * @memberof PIXI.DisplayObject# * @param {PIXI.CanvasRenderer} renderer - The canvas renderer */ -DisplayObject.prototype._renderCachedCanvas = function _renderCachedCanvas(renderer: AbstractRenderer): void +DisplayObject.prototype._renderCachedCanvas = function _renderCachedCanvas(renderer: IRenderer): void { if (!this.visible || this.worldAlpha <= 0 || !this.renderable) { @@ -387,7 +389,7 @@ DisplayObject.prototype._initCachedDisplayObjectCanvas = function _initCachedDis this.alpha = 1; - const cachedRenderTarget = renderer.context; + const cachedRenderTarget = renderer.canvasContext.activeContext; const cachedProjectionTransform = (renderer as any)._projTransform; bounds.ceil(settings.RESOLUTION); @@ -416,7 +418,7 @@ DisplayObject.prototype._initCachedDisplayObjectCanvas = function _initCachedDis renderer.render(this, { renderTexture, clear: true, transform: m, skipUpdateTransform: false }); // now restore the state be setting the new properties - renderer.context = cachedRenderTarget; + renderer.canvasContext.activeContext = cachedRenderTarget; (renderer as any)._projTransform = cachedProjectionTransform; this.renderCanvas = this._renderCachedCanvas; diff --git a/packages/prepare/src/BasePrepare.ts b/packages/prepare/src/BasePrepare.ts index fefeeadc25..d2a06ca34b 100644 --- a/packages/prepare/src/BasePrepare.ts +++ b/packages/prepare/src/BasePrepare.ts @@ -5,7 +5,7 @@ import { Container, DisplayObject } from '@pixi/display'; import { Text, TextStyle, TextMetrics } from '@pixi/text'; import { CountLimiter } from './CountLimiter'; -import type { AbstractRenderer } from '@pixi/core'; +import type { IRenderer } from '@pixi/core'; interface IArrowFunction { @@ -13,7 +13,7 @@ interface IArrowFunction } interface IUploadHook { - (helper: AbstractRenderer | BasePrepare, item: IDisplayObjectExtended): boolean; + (helper: IRenderer | BasePrepare, item: IDisplayObjectExtended): boolean; } interface IFindHook @@ -115,7 +115,7 @@ function findTexture(item: IDisplayObjectExtended, queue: Array): boolean * @param item - Item to check * @returns If item was uploaded. */ -function drawText(_helper: AbstractRenderer | BasePrepare, item: IDisplayObjectExtended): boolean +function drawText(_helper: IRenderer | BasePrepare, item: IDisplayObjectExtended): boolean { if (item instanceof Text) { @@ -135,7 +135,7 @@ function drawText(_helper: AbstractRenderer | BasePrepare, item: IDisplayObjectE * @param item - Item to check * @returns If item was uploaded. */ -function calculateTextStyle(_helper: AbstractRenderer | BasePrepare, item: IDisplayObjectExtended): boolean +function calculateTextStyle(_helper: IRenderer | BasePrepare, item: IDisplayObjectExtended): boolean { if (item instanceof TextStyle) { @@ -235,7 +235,7 @@ export class BasePrepare private limiter: CountLimiter; /** Reference to the renderer. */ - protected renderer: AbstractRenderer; + protected renderer: IRenderer; /** * The only real difference between CanvasPrepare and Prepare is what they pass @@ -277,9 +277,9 @@ export class BasePrepare private delayedTick: IArrowFunction; /** - * @param {PIXI.AbstractRenderer} renderer - A reference to the current renderer + * @param {PIXI.IRenderer} renderer - A reference to the current renderer */ - constructor(renderer: AbstractRenderer) + constructor(renderer: IRenderer) { this.limiter = new CountLimiter(settings.UPLOADS_PER_FRAME); this.renderer = renderer; diff --git a/packages/prepare/src/Prepare.ts b/packages/prepare/src/Prepare.ts index 441a5d6fd7..f9391de897 100644 --- a/packages/prepare/src/Prepare.ts +++ b/packages/prepare/src/Prepare.ts @@ -2,7 +2,7 @@ import { BaseTexture } from '@pixi/core'; import { Graphics } from '@pixi/graphics'; import { BasePrepare, IDisplayObjectExtended } from './BasePrepare'; -import type { AbstractRenderer, Renderer } from '@pixi/core'; +import type { Renderer, IRenderer } from '@pixi/core'; /** * Built-in hook to upload PIXI.Texture objects to the GPU. @@ -11,7 +11,7 @@ import type { AbstractRenderer, Renderer } from '@pixi/core'; * @param item - Item to check * @returns If item was uploaded. */ -function uploadBaseTextures(renderer: AbstractRenderer | BasePrepare, item: IDisplayObjectExtended | BaseTexture): boolean +function uploadBaseTextures(renderer: IRenderer | BasePrepare, item: IDisplayObjectExtended | BaseTexture): boolean { if (item instanceof BaseTexture) { @@ -36,7 +36,7 @@ function uploadBaseTextures(renderer: AbstractRenderer | BasePrepare, item: IDis * @param item - Item to check * @returns If item was uploaded. */ -function uploadGraphics(renderer: AbstractRenderer | BasePrepare, item: IDisplayObjectExtended): boolean +function uploadGraphics(renderer: IRenderer | BasePrepare, item: IDisplayObjectExtended): boolean { if (!(item instanceof Graphics)) { diff --git a/packages/prepare/test/BasePrepare.tests.ts b/packages/prepare/test/BasePrepare.tests.ts index 7283038050..4de6363b17 100644 --- a/packages/prepare/test/BasePrepare.tests.ts +++ b/packages/prepare/test/BasePrepare.tests.ts @@ -1,14 +1,14 @@ import { BasePrepare } from '@pixi/prepare'; import sinon from 'sinon'; import { expect } from 'chai'; -import { AbstractRenderer } from '@pixi/core'; import { DisplayObject } from '@pixi/display'; +import { IRenderer } from '@pixi/core'; describe('BasePrepare', () => { it('should create a new, empty, BasePrepare', () => { - const renderer = {} as AbstractRenderer; + const renderer = {} as IRenderer; const prep = new BasePrepare(renderer); expect(prep['renderer']).to.equal(renderer); @@ -25,7 +25,7 @@ describe('BasePrepare', () => { function addHook() { return true; } function uploadHook() { return true; } - const renderer = {} as AbstractRenderer; + const renderer = {} as IRenderer; const prep = new BasePrepare(renderer); prep.registerFindHook(addHook); @@ -41,7 +41,7 @@ describe('BasePrepare', () => it('should call hooks and complete', () => { - const renderer = {} as AbstractRenderer; + const renderer = {} as IRenderer; const prep = new BasePrepare(renderer); const uploadItem = {} as DisplayObject; const uploadHelper = {}; @@ -82,7 +82,7 @@ describe('BasePrepare', () => it('should call complete if no queue', () => { - const renderer = {} as AbstractRenderer; + const renderer = {} as IRenderer; const prep = new BasePrepare(renderer); function addHook() @@ -101,7 +101,7 @@ describe('BasePrepare', () => it('should remove un-preparable items from queue', () => { - const renderer = {} as AbstractRenderer; + const renderer = {} as IRenderer; const prep = new BasePrepare(renderer); const addHook = sinon.spy((item, queue) => @@ -132,7 +132,7 @@ describe('BasePrepare', () => it('should remove destroyed items from queue', () => { - const renderer = {} as AbstractRenderer; + const renderer = {} as IRenderer; const prep = new BasePrepare(renderer); const addHook = sinon.spy((item, queue) => @@ -166,7 +166,7 @@ describe('BasePrepare', () => it('should attach to the system ticker', (done) => { - const renderer = {} as AbstractRenderer; + const renderer = {} as IRenderer; const prep = new BasePrepare(renderer); const addHook = sinon.spy((item, queue) => diff --git a/packages/text-bitmap/src/BitmapText.ts b/packages/text-bitmap/src/BitmapText.ts index 36137be93f..1a7c3cdddb 100644 --- a/packages/text-bitmap/src/BitmapText.ts +++ b/packages/text-bitmap/src/BitmapText.ts @@ -625,9 +625,11 @@ export class BitmapText extends Container const fontScale = this._fontSize / size; + const resolution = renderer._view.resolution; + for (const mesh of this._activePagesMeshData) { - mesh.mesh.shader.uniforms.uFWidth = worldScale * distanceFieldRange * fontScale * this._resolution; + mesh.mesh.shader.uniforms.uFWidth = worldScale * distanceFieldRange * fontScale * resolution; } }